【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
",ans);
108     }
109 return 0;
110 }
View Code
原文地址:https://www.cnblogs.com/LQLlulu/p/9892390.html