【BZOJ2806】Cheat 【广义后缀自动机+单调队列优化dp+二分】
题意
有M篇标准作文组成了一个作文库(每篇作文都是一个01的字符串),然后给出N篇作文(自然也是01字符串)。如果一个长度不小于L的串在作文库中出现过,那么它是熟悉的。对于某一篇作文,我们要把它分为若干段,使得熟悉过的字符串长度>=百分之90,我们要求满足这个条件的最小的L。
分析
这个L显然满足二分,然后我们要想怎么判断,对于当前L,这篇作文的熟悉过字符串的最长长度是什么。我们先把作文库建一个广义后缀自动机,然后对于每篇作文很容易可以求出一个len[i]指的是在i位置结束的子串在作文库中出现过的最长长度是多少。然后我们来dp。设f[i]为前缀i的最长熟悉长度。那么f[i]=max(f[i-1],f[j]+i-j+1|(i-L>=j>=len[i]-i))。但是这个dp是要O(n^2)的。但是我们发现,这个dp是单调的,也就是说i如果是从j递推来的,那么i+1 就不可能从j以前递推来。所以这个j我们用单调队列来维护。我们单调队列中存的是根据值f[j]-j单调的j值。
1 #include <cstdio> 2 #include <cstring> 3 #include <iostream> 4 #include <algorithm> 5 #include <queue> 6 7 using namespace std; 8 const int maxn=3000000; 9 char s[maxn]; 10 struct state{ 11 int len,link; 12 int next[2]; 13 }st[2*maxn]; 14 int len[maxn]; 15 int N,M,n; 16 int last,cur,sz; 17 void init(){ 18 sz=1; 19 cur=last=0; 20 st[0].link=-1; 21 st[0].len=0; 22 } 23 void build_sam(int c){ 24 cur=sz++; 25 st[cur].len=st[last].len+1; 26 int p; 27 for(p=last;p!=-1&&st[p].next[c]==0;p=st[p].link) 28 st[p].next[c]=cur; 29 if(p==-1) 30 st[cur].link=0; 31 else{ 32 int q=st[p].next[c]; 33 if(st[q].len==st[p].len+1){ 34 st[cur].link=q; 35 }else{ 36 int clone=sz++; 37 st[clone].len=st[p].len+1; 38 st[clone].link=st[q].link; 39 for(int i=0;i<2;i++) 40 st[clone].next[i]=st[q].next[i]; 41 for(;p!=-1&&st[p].next[c]==q;p=st[p].link) 42 st[p].next[c]=clone; 43 st[cur].link=st[q].link=clone; 44 } 45 } 46 last=cur; 47 } 48 int f[maxn]; 49 //dp[i]=max(dp[j]+i-j| i-len[i]<=j<=i-L) 50 51 bool check(int L){ 52 deque<int>q; 53 for(int i=1;i<=n;i++){ 54 f[i]=f[i-1]; 55 if(i<L)continue; 56 while(!q.empty()&&f[q.back()]-q.back()<f[i-L]-i+L) 57 q.pop_back(); 58 q.push_back(i-L); 59 while(!q.empty()&&q.front()<i-len[i]) 60 q.pop_front(); 61 if(!q.empty()) 62 f[i]=max(f[i],f[q.front()]+i-q.front()); 63 } 64 return f[n]*10>=n*9; 65 } 66 67 int main(){ 68 scanf("%d%d",&N,&M); 69 init(); 70 for(int i=1;i<=M;i++){ 71 scanf("%s",s); 72 n=strlen(s); 73 for(int j=0;j<n;j++){ 74 int c=s[j]-'0'; 75 build_sam(c); 76 } 77 last=0; 78 } 79 for(int q=1;q<=N;q++){ 80 scanf("%s",s+1); 81 n=strlen(s+1); 82 int u=0,Len=0; 83 for(int i=1;i<=n;i++){ 84 int c=s[i]-'0'; 85 while(u!=-1&&st[u].next[c]==0){ 86 u=st[u].link; 87 Len=st[u].len; 88 } 89 if(u==-1) 90 u=0,Len=0; 91 else{ 92 u=st[u].next[c]; 93 Len++; 94 } 95 len[i]=Len; 96 } 97 int l=0,r=n,ans=0; 98 while(l<=r){ 99 int mid=l+(r-l)/2; 100 if(check(mid)){ 101 ans=mid; 102 l=mid+1; 103 }else{ 104 r=mid-1; 105 } 106 } 107 printf("%d\n",ans); 108 } 109 return 0; 110 }