CF700E Cool Slogans
给定一个字符串 (S),要求构造字符串序列 (s_1,s_2,ldots,s_k),满足任意 (s_i) 都是 (S) 的子串,且任意 (iin[2,n]),都有 (s_i) 在 (s_{i-1}) 中出现了至少 (2) 次(可以有重叠部分,只要起始、结尾位置不同即可)。 求可能的最大的 (k) 的值(即序列的最大可能长度)。
(1leq nleq 2 imes 10^5)。
题解
https://www.luogu.com.cn/blog/xht37/solution-cf700e
对字符串建 SAM,用可持久化线段树合并求出每个节点的 (operatorname{endpos}) 集合。
在 (operatorname{parent}) 树上从根向下 dp,设 (f_i) 表示到节点 (i) 时的最大值。
如果一个父节点的子串在子节点的子串中出现了至少两次,则转移时 (f) 加一,否则不变。
考虑如何判断是否出现了至少两次,设此时的子节点为 (x),父节点为 (pa_x)。
找到 (x) 对应的 (operatorname{endpos}) 中的任意一个位置 (pos),则 (pos) 处 (pa_x) 的子串一定出现了一次。
那么另一次只要在 ([pos - operatorname{len}(x) + operatorname{len}(pa_x), pos - 1]) 中有出现就行了。
总时间复杂度 (mathcal O(n log n))。
CO int N=4e5+10;
namespace seg{
int tot,lc[N*20],rc[N*20];
#define mid ((l+r)>>1)
void insert(int&x,int l,int r,int p){
if(!x) x=++tot;
if(l==r) return;
if(p<=mid) insert(lc[x],l,mid,p);
else insert(rc[x],mid+1,r,p);
}
int merge(int x,int y){
if(!x or !y) return x+y;
int z=++tot;
lc[z]=merge(lc[x],lc[y]);
rc[z]=merge(rc[x],rc[y]);
return z;
}
bool query(int x,int l,int r,int ql,int qr){
if(!x) return 0;
if(ql<=l and r<=qr) return 1;
if(qr<=mid) return query(lc[x],l,mid,ql,qr);
if(ql>mid) return query(rc[x],mid+1,r,ql,qr);
return query(lc[x],l,mid,ql,qr) or query(rc[x],mid+1,r,ql,qr);
}
#undef mid
}
int last=1,tot=1;
array<int,26> ch[N];
int fa[N],len[N],pos[N],root[N];
void extend(int c,int p){
int x=last,cur=last=++tot;
len[cur]=len[x]+1,pos[cur]=p;
for(;x and !ch[x][c];x=fa[x]) ch[x][c]=cur;
if(!x) {fa[cur]=1; return;}
int y=ch[x][c];
if(len[y]==len[x]+1) {fa[cur]=y; return;}
int clone=++tot;
ch[clone]=ch[y],fa[clone]=fa[y];
len[clone]=len[x]+1,pos[clone]=p;
fa[cur]=fa[y]=clone;
for(;ch[x][c]==y;x=fa[x]) ch[x][c]=clone;
}
char str[N];
int cnt[N],ord[N],F[N],G[N];
int main(){
int n=read<int>();
scanf("%s",str+1);
for(int i=1;i<=n;++i){
extend(str[i]-'a',i);
seg::insert(root[last],1,n,i);
}
for(int x=1;x<=tot;++x) ++cnt[len[x]];
for(int i=1;i<=n;++i) cnt[i]+=cnt[i-1];
for(int x=tot;x>=1;--x) ord[cnt[len[x]]--]=x;
for(int i=tot;i>=2;--i){
int x=ord[i],f=fa[x];
root[f]=seg::merge(root[f],root[x]);
}
int ans=1;
for(int i=2;i<=tot;++i){
int x=ord[i],f=fa[x];
if(f==1) {F[x]=1,G[x]=x; continue;}
if(seg::query(root[G[f]],1,n,pos[x]-len[x]+len[G[f]],pos[x]-1))
F[x]=F[f]+1,G[x]=x;
else F[x]=F[f],G[x]=G[f];
ans=max(ans,F[x]);
}
printf("%d
",ans);
return 0;
}
CF1063F String Journey
给定一个长度为(n)的只包含小写英文字母的字符串(s) ,你需要找到一个最大的(k) ,使得存在:
(即(k)个区间([l_1,r_1],[l_2,r_2]dots [l_k,r_k])的左右端点都递增且两两不相交)
使得对于每个(1le i < k) ,都满足(s[l_{i+1}dots r_{i+1}])是(s[l_idots r_i])的严格子串。
(1le nle 5 imes 10^5),字符串仅包含小写字母。
题解
https://www.cnblogs.com/xyz32768/p/12230395.html
-
性质1:存在一种最优方案,使得最后一段长为(1)且选出的每段长都比上一段小(1)。
证明:如果某一段比下一段长(2)或以上,则显然可以在这一段的前面或后面去掉若干字符,只需保证下一段是其严格子串,最后一段长为(1)的证明同理
于是我们发现从右往左DP更有拓展性。
-
性质2:答案不超过(O(sqrt n))。
证明:最坏情况下是(1 + 2 + 3 + dots + k),这时必然有(frac{k(k+1)}{2}leq n)。
-
性质3:以(i)为第一个串的开头,如果首串长为(j)时能选出(j)段,那么首串长为(j − 1)时也一定能选出(j − 1)段。
证明:可以用下面的图直观理解,红色表示(j)变成(j − 1)删去的部分。
于是我们可以把状态改成一维:(f[i])表示首串的开头为(i)最多能选出多少段。
求(f[i])可以先二分答案( ext{mid})。
然后现在要找的就是([i + ext{mid}, n])内是否存在一个j满足:
-
(f[j] ≥ ext{mid} − 1)。
-
(operatorname{lcp}(i,j) ≥ ext{mid} − 1)或(operatorname{lcp}(i + 1,j) ≥ ext{mid} − 1)。
注意到如果我们求出了原串的后缀数组,那么(operatorname{lcp}(i,j) ≥ ext{mid} − 1)相当于( ext{rank}_j)在某个区间内,(operatorname{lcp}(i + 1,j)≥ ext{mid} − 1)也是一样,这个区间可以用二分+RMQ求出。
问题转化成求(j in [i + ext{mid}, n])且满足( ext{rank}_j)在某区间内的(f[j])最大值。
可持久化线段树即可维护,(O(n log^2 n))。
-
性质4:(f[i] ≤ f[i + 1]+ 1)
证明类似性质3,即对于一个首串从(i)开始,有(j)个串的方案,如果删掉首串的第一个位置,那么一定可以用类似性质3的调整法得到一个从(i + 1)开始,有(j − 1)个串的方案。
于是我们可以把二分改成先让(f[i] leftarrow f[i + 1]+ 1)之后,判断当前是否存在选出(f[i])个串的方案,如果不行就让(f[i])一直减一,直到合法为止。
类似于SA求( ext{height}),可以得出总复杂度为(O(n log n))。
CO int N=1e6+10;
char str[N];
int last=1,tot=1;
array<int,26> ch[N];
int fa[N],len[N],pos[N],val[N],idx[N];
void extend(int c,int p){
int x=last,cur=last=++tot;
len[cur]=len[x]+1,pos[cur]=p,idx[p]=cur;
for(;!ch[x][c];x=fa[x]) ch[x][c]=cur;
if(!x) {fa[cur]=1; return;}
int y=ch[x][c];
if(len[y]==len[x]+1) {fa[cur]=y; return;}
int clone=++tot;
ch[clone]=ch[y],fa[clone]=fa[y],len[clone]=len[x]+1,pos[clone]=pos[cur];
fa[cur]=fa[y]=clone;
for(;ch[x][c]==y;x=fa[x]) ch[x][c]=clone;
}
array<int,26> son[N];
int anc[N][20],L[N],R[N],dfn;
void dfs(int x){
L[x]=idx[pos[x]]==x?++dfn:dfn+1;
for(int i=1;i<=19;++i) anc[x][i]=anc[anc[x][i-1]][i-1];
for(int c=0;c<26;++c)if(son[x][c]){
anc[son[x][c]][0]=x;
dfs(son[x][c]);
}
R[x]=dfn;
}
int find(int x,int l){
for(int i=19;i>=0;--i)
if(len[anc[x][i]]>=l) x=anc[x][i];
return x;
}
namespace seg{
int root[N],tot;
int lc[N*10],rc[N*10],val[N*10];
#define mid ((l+r)>>1)
void insert(int&x,int l,int r,int p,int v){
++tot,lc[tot]=lc[x],rc[tot]=rc[x];
val[tot]=max(val[x],v),x=tot;
if(l==r) return;
if(p<=mid) insert(lc[x],l,mid,p,v);
else insert(rc[x],mid+1,r,p,v);
}
int query(int x,int l,int r,int ql,int qr){
if(!x) return 0;
if(ql<=l and r<=qr) return val[x];
if(qr<=mid) return query(lc[x],l,mid,ql,qr);
if(ql>mid) return query(rc[x],mid+1,r,ql,qr);
return max(query(lc[x],l,mid,ql,qr),query(rc[x],mid+1,r,ql,qr));
}
#undef mid
}
int F[N];
int main(){
int n=read<int>();
scanf("%s",str+1);
for(int i=n;i>=1;--i) extend(str[i]-'a',i);
for(int i=tot;i>=2;--i) son[fa[i]][str[pos[i]+len[fa[i]]]-'a']=i;
dfs(1);
function<bool(int,int)> check=[&](int i,int l)->bool{
int x=idx[i],f=find(x,l-1);
if(seg::query(seg::root[i+l],1,dfn,L[f],R[f])>=l-1) return 1;
x=idx[i+1],f=find(x,l-1);
if(seg::query(seg::root[i+l],1,dfn,L[f],R[f])>=l-1) return 1;
return 0;
};
int ans=0;
for(int i=n;i>=1;--i){
F[i]=F[i+1]+1;
while(!check(i,F[i])) --F[i];
seg::insert(seg::root[i]=seg::root[i+1],1,dfn,L[idx[i]],F[i]);
ans=max(ans,F[i]);
}
printf("%d
",ans);
return 0;
}