Codeforces 700E. Cool Slogans 字符串,SAM,线段树合并,动态规划
原文链接https://www.cnblogs.com/zhouzhendong/p/CF700E.html
题解
首先建个SAM。
一个结论:对于parent树上任意一个点x,以及它所代表的子树内任意一个点y,设节点y代表的最长串为S,设节点x代表的串为T1,T2,T3,...,设 F(S,T) 表示串T在S中的出现次数,则 F(S,T1) = F(S,T2) = F(S,T3) = ...
证明:假设串 Ta 和 Tb 在 S 中的出现次数不同,且 |Ta|+1=|Tb| 则必然存在一个位置,使得将 Tb 放在这里的时候,它的最左端点不和 S 匹配,其他位置都匹配,这样的话,Tb 的 Right 集合至少比 Ta 多这个位置,与 “Ta,Tb 都是节点 x 代表的串” 矛盾。故原命题得证。
第二个结论:一定存在一个最优解,使得 $\forall 1<i\leq k$, $S_{i-1}$ 是 $S_i$ 的 Border 。
这个很好证,如果不是 Border ,把 $S_i$ 两边多出来的割掉一定不亏。
于是我们可以开始规划算法了。
设 $dp[S]$ 表示每次保证前一个串在后一个串中出现至少 2 次,从空串转移到串 $S$ 的最多转移次数。
我们把状态用 parent 树上的节点表示,由于第一个结论,对于每一个节点,我们可以只把这个节点代表的最长串作为有效状态;转移的时候,只要看看父亲的串在当前节点的串中出现次数是否至少2次,如果不到,就直接继承父亲的结果,否则更新为当前结果; 判断出现多少次需要处理出 Right 集合,线段树合并即可。
代码
#include <bits/stdc++.h>
#define clr(x) memset(x,0,sizeof (x))
using namespace std;
typedef long long LL;
LL read(){
LL x=0,f=0;
char ch=getchar();
while (!isdigit(ch))
f|=ch=='-',ch=getchar();
while (isdigit(ch))
x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
return f?-x:x;
}
const int N=200005*2;
int n;
namespace seg{
const int S=N*50;
int ls[S],rs[S],size[S],cnt,root;
void Init(){
cnt=root=0;
clr(ls),clr(rs),clr(size);
}
void Ins(int &rt,int L,int R,int x){
if (!rt)
rt=++cnt;
size[rt]++;
if (L==R)
return;
int mid=(L+R)>>1;
if (x<=mid)
Ins(ls[rt],L,mid,x);
else
Ins(rs[rt],mid+1,R,x);
}
int Merge(int a,int b,int L,int R){
if (!a||!b)
return a+b;
int rt=++cnt;
if (L==R)
size[rt]=1;
else {
int mid=(L+R)>>1;
ls[rt]=Merge(ls[a],ls[b],L,mid);
rs[rt]=Merge(rs[a],rs[b],mid+1,R);
size[rt]=size[ls[rt]]+size[rs[rt]];
}
return rt;
}
int Query(int rt,int L,int R,int xL,int xR){
if (!rt||xL>R||L>xR)
return 0;
if (xL<=L&&R<=xR)
return size[rt];
int mid=(L+R)>>1;
return Query(ls[rt],L,mid,xL,xR)
+Query(rs[rt],mid+1,R,xL,xR);
}
}
namespace SAM{
int last,size,root;
struct Node{
int Next[26],fa,Max,pos;
}t[N];
int Init(){
clr(t);
return last=size=root=1;
}
void extend(int c,int ps){
int p=last,np=++size,q,nq;
t[np].Max=t[p].Max+1,t[np].pos=ps;
for (;p&&!t[p].Next[c];p=t[p].fa)
t[p].Next[c]=np;
if (!p)
t[np].fa=root;
else {
q=t[p].Next[c];
if (t[p].Max+1==t[q].Max)
t[np].fa=q;
else {
nq=++size;
t[nq]=t[q],t[nq].Max=t[p].Max+1,t[nq].pos=ps;
t[np].fa=t[q].fa=nq;
for (;p&&t[p].Next[c]==q;p=t[p].fa)
t[p].Next[c]=nq;
}
}
last=np;
}
int id[N],tax[N],rt[N];
void sort(){
clr(tax);
for (int i=1;i<=size;i++)
tax[t[i].Max]++;
for (int i=1;i<=size;i++)
tax[i]+=tax[i-1];
for (int i=1;i<=size;i++)
id[tax[t[i].Max]--]=i;
}
void build(){
sort();
seg::Init();
for (int i=size;i>1;i--)
seg::Ins(rt[id[i]],1,n,t[id[i]].pos);
for (int i=size;i>1;i--){
int x=id[i],f=t[x].fa;
rt[f]=seg::Merge(rt[f],rt[x],1,n);
}
}
int dp[N],nid[N];
int Horse_NMDP(){
int ans=0;
dp[1]=0,nid[1]=1;
for (int i=2;i<=size;i++){
int x=id[i],f=nid[t[x].fa];
if (f==1||seg::Query(rt[f],1,n,t[x].pos-t[x].Max+t[f].Max
,t[x].pos)>=2)
dp[x]=dp[f]+1,nid[x]=x;
else
dp[x]=dp[f],nid[x]=f;
ans=max(ans,dp[x]);
}
return ans;
}
}
using SAM::t;
using SAM::extend;
char s[N];
int main(){
n=read();
scanf("%s",s+1);
SAM::Init();
for (int i=1;i<=n;i++)
extend(s[i]-'a',i);
SAM::build();
cout<<SAM::Horse_NMDP()<<endl;
return 0;
}
Codeforces 700E. Cool Slogans 字符串,SAM,线段树合并,动态规划的更多相关文章
- Codeforces.700E.Cool Slogans(后缀自动机 线段树合并 DP)
题目链接 \(Description\) 给定一个字符串\(s[1]\).一个字符串序列\(s[\ ]\)满足\(s[i]\)至少在\(s[i-1]\)中出现过两次(\(i\geq 2\)).求最大的 ...
- 【Codeforces 1037H】Security(SAM & 线段树合并)
Description 给出一个字符串 \(S\). 给出 \(Q\) 个操作,给出 \(L, R, T\),求字典序最小的 \(S_1\),使得 \(S^\prime\) 为\(S[L..R]\) ...
- Codeforces 1276F - Asterisk Substrings(SAM+线段树合并+虚树)
Codeforces 题面传送门 & 洛谷题面传送门 SAM hot tea %%%%%%% 首先我们显然可以将所有能够得到的字符串分成六类:\(\varnothing,\text{*},s, ...
- 洛谷P4482 [BJWC2018]Border 的四种求法 字符串,SAM,线段树合并,线段树,树链剖分,DSU on Tree
原文链接https://www.cnblogs.com/zhouzhendong/p/LuoguP4482.html 题意 给定一个字符串 S,有 q 次询问,每次给定两个数 L,R ,求 S[L.. ...
- UOJ#395. 【NOI2018】你的名字 字符串,SAM,线段树合并
原文链接https://www.cnblogs.com/zhouzhendong/p/UOJ395.html 题解 记得同步赛的时候这题我爆0了,最暴力的暴力都没调出来. 首先我们看看 68 分怎么做 ...
- CF700E-Cool Slogans【SAM,线段树合并,dp】
正题 题目链接:https://www.luogu.com.cn/problem/CF700E 题目大意 给出一个字符串\(S\),求一个最大的\(k\)使得存在\(k\)个字符串其中\(s_1\)是 ...
- loj#2059. 「TJOI / HEOI2016」字符串 sam+线段树合并+倍增
题意:给你一个子串,m次询问,每次给你abcd,问你子串sa-b的所有子串和子串sc-d的最长公共前缀是多长 题解:首先要求两个子串的最长公共前缀就是把反过来插入变成最长公共后缀,两个节点在paren ...
- CodeForces - 666E: Forensic Examination (广义SAM 线段树合并)
题意:给定字符串S,然后M个字符串T.Q次询问,每次给出(L,R,l,r),问S[l,r]在L到R这些T字符串中,在哪个串出现最多,以及次数. 思路:把所有串建立SAM,然后可以通过倍增走到[l,r] ...
- 2019.02.27 bzoj4556: [Tjoi2016&Heoi2016]字符串(二分答案+sam+线段树合并)
传送门 题意:给一个字符串SSS. 有mmm次询问,每次给四个参数a,b,c,da,b,c,da,b,c,d,问s[a...b]s[a...b]s[a...b]的所有子串和s[x...y]s[x... ...
随机推荐
- Scrapy 框架 中间件,信号,定制命令
中间件 下载器中间件 写中间件 from scrapy.http import HtmlResponse from scrapy.http import Request class Md1(objec ...
- Win下端口占用问题:OSError: [WinError 10013] 以一种访问权限不允许的方式做了一个访问套接字的尝试
常见问题:https://www.cnblogs.com/dotnetcrazy/p/9192089.html netstat -ano|findstr 8080 如果不计较端口,换个即可 也可以查找 ...
- nginx设置目录浏览及解决中文乱码问题
在Nginx下默认是不允许列出整个目录的.如需开启此功能,先打开nginx.conf文件,在location server 或 http段中加入相关参数. http { include mime.ty ...
- BZOJ2870 最长道路
题意:给定树,有点权.求一条路径使得最小点权 * 总点数最大.只需输出这个最大值.5w. 解:树上路径问题,点分治. 考虑合并两个子树的时候,答案的形式是val1 * (d1 + d2),当1是新插入 ...
- Notepad++ 的函数参数提示错误的问题终于解决了
看第3张图片,明明我输入的是 print_double(), 提示的却是 print() 函数的参数. 这个问题困扰了我半年,今天晚上找到解决问题的办法:
- linux中的find命令常用场景
1.find file.txt 在当前目录下,查找file.txt是否存在 2.find . -name file.txt 在当前目录下,递归查找file.txt文件 ...
- 驱动调试(四)oops确定调用树
目录 驱动调试(四)oops确定调用树 内核开启调用树 栈指针分析 原理 寄存器别名 基础解释 例子分析 找到PC地址的位置 栈分析 附录:原文的excel title: 驱动调试(四)oops确定调 ...
- 老男孩Python全栈学习 S9 日常作业 003
1.有变量name = "aleX leNb" 完成如下操作: # 移除 name 变量对应的值两边的空格,并输出处理结果 # 移除name变量左边的"al"并 ...
- 第三节:Action向View传值的四种方式(ViewData、ViewBag、TempData、Model)
简 介 在前面的章节中,我们已经很清楚,MVC工作模型的流程,Controller中的Action接收到客户端的请求,处理后要将数据返回给View,那么Action中是如何将数据返回给View的,二 ...
- 第十节:数据批注(DataAnnotationModel)和自定义验证(包括Model级别的验证)
一. 简介 写完上一个章节MVC中的常用特性,迫不及待将该系列补全,该章节主要介绍数据批注(也叫:注解). 一听[数据批注],好高大上的名字,但仔细一看,它们其实是[System.ComponentM ...