bzoj 2806 [Ctsc2012]Cheat——广义后缀自动机+单调队列优化DP
题目:https://www.lydsy.com/JudgeOnline/problem.php?id=2806
只想着怎么用后缀数据结构做,其实应该考虑结合其他算法。
可以二分那个长度 L 。设当前二分为 mid ;令 dp[ i ] 表示到 i 位置“熟悉”的最大长度。那么 \( dp[i]=\max(dp[i-1],\max\limits_{j<=i-mid,s[j+1...i] \in S}(dp[j]+(i-j)) ) \) (其中 S 是模式串的所有子串集合)。
关于那个判断,只要先作出以询问串的每个位置 i 为结尾最长能匹配的后缀长度 f[ i ] 就行了。
这个 DP 过程可以用单调队列优化。在 i 位置把 i-mid 的值加入队列。
注意匹配的长度不是自动机对应点的 len ,而是要记一个 ct ,如果顺延的话,ct++ 而不是 ct = len[ go[cr][w] ] 这样。
注意 dp 的时候如果 i < mid ,不仅是不往队列加元素,还不能做转移(比如 s[ 1...i ] 在模式串里出现也不能给 dp[ i ] 赋值)!因为这样匹配上的是长度 <mid 的,不合法。
#include<cstdio> #include<cstring> #include<algorithm> #include<cmath> using namespace std; int Mx(int a,int b){return a>b?a:b;} const int N=11e5+5; int n,m,go[N][2],len[N],fa[N],cnt=1,lst; int f[N],dp[N],q[N],he,tl; char s[N]; int cz(int p,int w) { int q=go[p][w],nq=++cnt; len[nq]=len[p]+1; fa[nq]=fa[q]; fa[q]=nq; memcpy(go[nq],go[q],sizeof go[q]); for(;go[p][w]==q;p=fa[p])go[p][w]=nq; return nq; } int ins(int p,int w) { if(go[p][w]) { int q=go[p][w]; if(len[q]==len[p]+1)return q; return cz(p,w); } int np=++cnt; len[np]=len[p]+1; for(;p&&!go[p][w];p=fa[p])go[p][w]=np; if(!p){ fa[np]=1;return np;}//fa=1! int q=go[p][w]; if(len[q]==len[p]+1)fa[np]=q; else fa[np]=cz(p,w); return np; } int chk(int mid,int d) { he=tl=0; for(int i=1;i<=d;i++) { dp[i]=dp[i-1]; if(i<mid)continue;//not do!!! int cr=i-mid; while(he<tl&&dp[q[tl]]-q[tl]<=dp[cr]-cr)tl--; q[++tl]=cr; while(he<tl&&q[he+1]<i-f[i])he++; if(he<tl)dp[i]=Mx(dp[i],dp[q[he+1]]+i-q[he+1]); } return dp[d]; } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=m;i++) { scanf("%s",s+1); for(int j=1,lm=strlen(s+1),d=1;j<=lm;j++) d=ins(d,s[j]-'0'); } for(int i=1,d;i<=n;i++) { scanf("%s",s+1); d=strlen(s+1); for(int j=1,cr=1,ct=0;j<=d;j++)//ct not len[cr]!! { int w=s[j]-'0'; while(!go[cr][w])cr=fa[cr],ct=len[cr]; if(go[cr][w])cr=go[cr][w],ct++; else cr=1,ct=0; f[j]=ct; } int lm=ceil(0.9*d),l=1,r=d,ret=0; while(l<=r) { int mid=l+r>>1; if(chk(mid,d)>=lm)ret=mid,l=mid+1; else r=mid-1; } printf("%d\n",ret); } return 0; }