题目描述:就是给你多个01串当作字典然后给你另一个01串问你这个01串长度90%以上被匹配时的分段匹配的段的最大值L0(这个最大值是当前分段每一段的最小值,就是每一段长度都大于这个值,求这个值的最大值)
我看到这道题就知道要把所有的字典的串合成一个串来搞,原来弄过一个AC自动机的题目和这个描述很像,自然而然就这么弄了,建立SAM的时候每一个串之间弄个分隔符。原因不说了。现在我们只要先求出每一个位置向前能够匹配的最大值f(i)为什么只求最大值呢?因为每一个位置最大值能够向前匹配,那么小于最大值的串显然是最大值的后缀子串,显然仍然能够匹配,所以只需要求每个位置的能够匹配的最大值就可以了。然后怎么搞呢枚举每一段显然不可能,那这种情况下二分吧,二分当前的L0 然后怎么检测当前的L0是否可用呢,另dp(i)为1-i位能够被L0匹配的最大数量
首先因为dp(i)>=dp(i−1)所以我们不用看i−f(i)之前的位置,因为dp(i−f(i))>=dp(i−f(i)−k)然后发现长度起码为L0所以我们只需要关注[i−f(i),i−L0]这个区间内的dp值就行了
所以让dp(i)最大那么dp(i)=max(dp(j)+(i−j))i−f(i)−1<j<i−L0+1那么这个算法的复杂度是O(n2logn)显然满足不了,那么进行优化dp(i)=max(dp(j)−j)+i那么dp(i)−i=max(dp(j)−j)那么我们另g(i)=dp(i)−i求每一个点之前的区间内的最大值,试试看单调队列优化,如果要用单调队列优化需要证明i−f(i)是单调递增的,需要证明f(i)是单调递减的那么我们发现如果有一个发现如果前一个串a2a3...an显然是a2a3...anan+1如果a1a2a3...anan+1可以被匹配那么显然a1a2a3...an也可以被匹配那么前一个就不是a2a3...an那么f(i+1)<=f(i)+1那么这个时候区间的左端点要么向右移动要么不动,然后现在用单调队列优化就行了,每次显然是多了一个g(i−L0) 。。。wa了5个小时,发现是忘了清零。。。好了现在复杂度变成O(nlogn)
#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;
}