【后缀自动机】[BZOJ 2806]Cheat
题目描述:就是给你多个01串当作字典然后给你另一个01串问你这个01串长度90%以上被匹配时的分段匹配的段的最大值
我看到这道题就知道要把所有的字典的串合成一个串来搞,原来弄过一个AC自动机的题目和这个描述很像,自然而然就这么弄了,建立SAM的时候每一个串之间弄个分隔符。原因不说了。现在我们只要先求出每一个位置向前能够匹配的最大值
首先因为
所以让
#include <cstdio>
#include <cstring>
#include <queue>
#include <algorithm>
using namespace std;
const int MAXN = 2200010;
const int INF = 100000000;
struct node{
node *p[4], *pre;
int len;
}Edges[MAXN*2+10], *ecnt=Edges+1,*root=Edges,*last=Edges, *st[MAXN*2+10];
void Insert(int w){
node *np = ecnt++;
node *p = last;
np->len = p->len+1;
while(p&&!p->p[w])
p->p[w]=np, p=p->pre;
if(!p){
np->pre = root;
}else{
node *q = p->p[w];
if(p->len+1 == q->len){
np->pre = q;
}else{
node *nnd = ecnt++;
memcpy(nnd->p, q->p, sizeof (nnd->p));
nnd->len = p->len+1; nnd->pre = q->pre; q->pre = nnd; np->pre = nnd;
while(p&&p->p[w]==q)
p->p[w]=nnd, p=p->pre;
}
}
last = np;
}
const int MAXT = 2200010;
char s[MAXT+10];
int dp[MAXT+10], dp2[MAXT+10], que[MAXT];
bool check(int l){
//dp2[i] = max{dp2[j]+i-j} i-out dp2[i]-i = max{dp2[j]-j}//
int len = strlen(s);
int lk=0, rk=0, ins;
for(int i=0;i<l;i++)
dp2[i] = 0;
for(int i=l;i<=len;i++){
dp2[i] = dp2[i-1];
ins = i-l;
while(lk<rk&&dp2[ins]-ins>=dp2[que[rk-1]]-que[rk-1]) rk--;
que[rk++] = ins;
while(lk<rk&&que[lk]<i-dp[i]) lk++;
if(lk < rk)
dp2[i] = max(dp2[i], dp2[que[lk]] - que[lk] + i);
}
return dp2[len] * 10 >= len * 9;
}
int main(){
int n, m;
scanf("%d %d", &n, &m);
for(int i=0;i<m;i++){
scanf("%s", s);
int len = strlen(s);
for(int j=0;j<len;j++)
Insert(s[j]-'0');
Insert(2);
}
for(int i=0;i<n;i++){
node *now = root;
scanf("%s", s);
int L = 0, len = strlen(s);
for(int j=0;j<len;j++){
int idx = s[j]-'0';
if(now->p[idx]){
now = now->p[idx];
L++;
}else{
while(now && !now->p[idx])
now = now->pre;
if(!now){
now = root;
L = 0;
}else{
L = now->len + 1;
now = now->p[idx];
}
}
dp[j+1] = L;
}
int l=1, r=len, ans=0;
while(l <= r){
int mid = (l + r) >> 1;
if(check(mid)){ l = mid+1; ans = mid;
}else r = mid-1;
}
printf("%d\n", ans);
}
return 0;
}