CF700E Cool Slogans
\(\text{Solution:}\)
\(dp,\) 思路都是对的 又死细节上了 对 SAM 的理解还是不够……(或者应该说是 \(dp\))
首先考虑一下什么情况我们可以接上一个串。题目给的是出现了两次,那转化到 SAM 上,我们如何用已知信息来判别?
首先证明一个结论,如果 \(t\) 可以接到当前串的后面,那必然存在一种最优方案使 \(s\) 是 \(t\) 的后缀。
若不是,考虑把后面多余的切掉不会劣。
那么就可以直接扔到 parent 树上考虑了。继续考虑在这种情况下我们如何判定。
假定我们已经求出了一个 \(t\) 的 endpos,
直接扔结论:
\[\exist i\in [endpos-len[t]+len[s],endpos-1],i\in endpos_s \]证明:首先其作为后缀已经出现了一次,剩下的一次需要在 \(endpos\) 之前,最右端也就是 \(endpos-1;\) 而左端至少要到起点加上其长度的位置。
那就可以愉快 \(dp\) 了,设 \(f[i]\) 表示以 parent 树上 \(i\) 节点结尾的最长长度,显然如果符合条件就直接 \(f[i]=f[x]+1.\) 不符合呢?
当时写的时候就默认给删了……实际上这样不对,显然舍去了一些答案呀……
观察一下,在树上一个后缀容易在深度更深的后缀中出现两次。
这也就意味着我们的直接舍去的做法是错的。所以我们还需要记录一下一个点的前驱状态。
初始的时候转移状态默认为自己,如果一个节点其父亲无法与当前点匹配,那就令其前驱状态为其父亲。
转移的时候均默认从前驱状态转移即可。复杂度是 \(O(n\log n),\) 因为需要线段树合并维护 endpos 来支持查询。
#include<bits/stdc++.h> using namespace std; const int N=5e5+10; const int TN=1e7+10; inline int Min(int x,int y){return x<y?x:y;} inline int Max(int x,int y){return x>y?x:y;} int n; char s[N]; namespace SGT{ int ls[TN],rs[TN],node; void change(int &x,const int &L,const int &R,const int &pos){ if(!x)x=++node; if(L==R)return; int mid=(L+R)>>1; if(pos<=mid)change(ls[x],L,mid,pos); else change(rs[x],mid+1,R,pos); } int merge(const int &x,const int &y){ if(!x||!y)return x|y; int p=++node; ls[p]=merge(ls[x],ls[y]); rs[p]=merge(rs[x],rs[y]); return p; } bool query(const int &x,const int &L,const int &R,const int &l,const int &r){ if(!x)return false; if(L>=l&&R<=r)return true; int mid=(L+R)>>1; bool res=false; if(l<=mid)res|=query(ls[x],L,mid,l,r); if(mid<r)res|=query(rs[x],mid+1,R,l,r); return res; } } using namespace SGT; namespace SAM{ int len[N],pa[N],ch[N][26],last=1,tot=1,col[N],minr[N],f[N],rt[N]; int siz[N],prestate[N]; vector<int>G[N]; queue<int>q; void insert(const int &c,const int &cl){ int p=last;int np=++tot;last=tot;siz[np]=1; len[np]=len[p]+1;col[np]=cl;minr[np]=cl; for(;p&&!ch[p][c];p=pa[p])ch[p][c]=np; if(!p)pa[np]=1; else{ int q=ch[p][c]; if(len[q]==len[p]+1)pa[np]=q; else{ int nq=++tot; len[nq]=len[p]+1; pa[nq]=pa[q];pa[q]=pa[np]=nq; memcpy(ch[nq],ch[q],sizeof ch[q]); for(;p&&ch[p][c]==q;p=pa[p])ch[p][c]=nq; } } } void dfs(int x){ if(col[x])change(rt[x],1,n,col[x]); for(auto v:G[x]){ dfs(v); rt[x]=merge(rt[x],rt[v]); minr[x]=Max(minr[x],minr[v]); siz[x]+=siz[v]; } } void Build(){ for(int i=2;i<=tot;++i)G[pa[i]].push_back(i); dfs(1); } void Dp(){ q.push(1);f[1]=0; while(!q.empty()){ int x=q.front(); q.pop(); for(auto v:G[x]){ int endpos=minr[v]; // printf("%d:[%d %d]\n",v,endpos-len[v]+len[x],endpos-1); if(query(rt[prestate[x]],1,n,endpos-len[v]+len[prestate[x]],endpos-1))f[v]=Max(f[v],f[prestate[x]]+1);//,cout<<"?\n"; else { f[v]=Max(f[v],f[x]),prestate[v]=prestate[x]; } q.push(v); } } } } using namespace SAM; int ep[N]; int main(){ // freopen("in.txt","r",stdin); scanf("%d",&n); scanf("%s",s+1); memset(minr,-0x3f,sizeof minr); for(int i=1;i<=n;++i){ int v=s[i]-'a'; ep[i]=tot+1; insert(v,i); } for(int i=1;i<=tot;++i)f[i]=1,prestate[i]=i; Build(); // for(int i=1;i<=n;++i){ // printf("%d %d %d\n",ep[i],siz[ep[i]],minr[ep[i]]); // } // for(int i=1;i<=tot;++i)cout<<minr[i]<<" "; // cout<<endl; Dp(); // for(int i=1;i<=tot;++i)cout<<f[i]<<" "; // puts(""); int ans=-1; for(int i=1;i<=tot;++i)ans=Max(ans,f[i]); printf("%d\n",ans); return 0; }