BZOJ 2806 [Ctsc2012]Cheat ——后缀自动机 单调队列优化DP
先建出广义后缀自动机。
然后跑出文章中每一个位置的最大匹配距离。
然后定义$f[i]$表示匹配到以$i$结尾的串时,最长的匹配距离。
显然可以二分$L$的取值。
然后容易得到$DP$方程
$f[i]=max(f[i-1],f[j]+i-j)(j<=i-L)$
然后就发现$j$属于一个区间,然后就可以单调队列优化了。
#include <map> #include <ctime> #include <cmath> #include <queue> #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; #define F(i,j,k) for (int i=j;i<=k;++i) #define D(i,j,k) for (int i=j;i>=k;--i) #define maxn 2200005 namespace SAM{ int last,cnt,go[maxn][2],l[maxn],fa[maxn],n,m,q; int f[maxn],que[maxn],hd,tl,d[maxn]; char s[maxn]; void init(){last=cnt=1;} void add(int x) { int q,p=last; if (q=go[p][x]) { if (l[q]==l[p]+1) last=q; else { int nq=last=++cnt; l[nq]=l[p]+1; memcpy(go[nq],go[q],sizeof go[q]); fa[nq]=fa[q];fa[q]=nq; for (;go[p][x]==q&&p;p=fa[p]) go[p][x]=nq; last=nq; } } else { int np=last=++cnt; l[np]=l[p]+1; for (;p&&!go[p][x];p=fa[p]) go[p][x]=np; if (!p) fa[np]=1; else { q=go[p][x]; if (l[q]==l[p]+1) fa[np]=q; else { int nq=++cnt;l[nq]=l[p]+1; memcpy(go[nq],go[q],sizeof go[q]); fa[nq]=fa[q]; fa[q]=fa[np]=nq; for (;p&&go[p][x]==q;p=fa[p]) go[p][x]=nq; } } } } void ins() { last=1; scanf("%s",s+1); n=strlen(s+1); F(i,1,n) add(s[i]-'0'); } bool check(int mid) { f[0]=0; tl=0;hd=1; F(i,1,n) { f[i]=f[i-1];int p=i-mid; if (p>=0) { while (hd<=tl&&f[que[tl]]-que[tl]<f[i-mid]-p)tl--; que[++tl]=p; } while (hd<=tl&&que[hd]<i-d[i]) hd++; if (hd<=tl) f[i]=max(f[i],f[que[hd]]+i-que[hd]); } if (f[n]*10>=n*9) return true; else return false; } void dp() { scanf("%s",s+1); n=strlen(s+1); memset(d,0,(n+1)*sizeof(int)); int now=1,t=0; F(i,1,n) { if (go[now][s[i]-'0']) now=go[now][s[i]-'0'],t++; else { while (now&&!go[now][s[i]-'0']) now=fa[now]; if (!now) now=1,t=0; else t=l[now]+1,now=go[now][s[i]-'0']; } d[i]=t; } int l=0,r=n; while (l<r) { int mid=(l+r)/2+1; if (check(mid)) l=mid; else r=mid-1; } printf("%d\n",l); } void solve() { init(); scanf("%d%d",&q,&m); F(i,1,m) ins(); F(i,1,q) dp(); } } int main() { using namespace SAM; solve(); }