Codeforces 700E. Cool Slogans 字符串,SAM,线段树合并,动态规划

时间:2024-08-15 09:07:44

原文链接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;
}