您的位置:首页 > 其它

bzoj2806 [Ctsc2012]Cheat(单调队列优化dp+二分+广义SAM)

2018-01-31 15:42 591 查看
题目链接

分析:

一看到这种最大值的问题,冥冥之中可以感知到是二分在召唤我们

我们把所有的标准串扔到SAMSAM里,建出一个广义后缀自动机

二分一个LL,用dp判断可行性

怎么用dp呢dp呢dp呢

其实这个问题,就是求把文章分段后的最大的匹配长度

我们先把模式串放到SAMSAM上跑一遍

得到lenlen数组,表示ii位置之前最大公共长度

得到这个有什么用呢?

设计DP状态:f[i]f[i]表示前ii个字符最长熟悉长度

对于当前我们考虑的状态,我们只要考虑它为某一段尾部最后一个字符就可以了

首先,如果当前位置的撑破天都达不到LL,我们就没有必要转移这个位置

其次,f[i]=f[i−1]f[i]=f[i−1],因为第ii位的匹配长度肯定不会小于前一个位置的匹配长度

再其次,当前状态的决策区间(上一个区间的结尾)只可能是[i−len,i−L][i−len,i−L]

因为小于i−leni−len的位置是不能匹配

那么我们很容易就可以得到转移:f[i]=max(f[i−1],f[j]+i−j|i−len[i]<=j<=i−L)f[i]=max(f[i−1],f[j]+i−j|i−len[i]<=j<=i−L)

其中[j+1,i][j+1,i]就是这一段的匹配长度

但是这样是O(n2)O(n2)的复杂度,我们能不能优化呢

因为i−len[i]i−len[i]严格单调不减,也就是说转移点单调不减,所以我们可以用单调队列优化

双端队列

队尾

强调一点:队列中的元素一定是能够转移此结点的状态

我们之前已经说过了,ii结点的转移区间只有 [i−len[i],i−L][i−len[i],i−L]

每当ii在向后移动的时候,唯一可能产生的新的转移点就是(i−L)(i−L)

那我们就在队尾插入这个转移点:(i−L)(i−L)

观察一下转移方程:f[i]=f[j]+i−jf[i]=f[j]+i−j

由转移点决定的值只有:f[j]−jf[j]−j

所以我们直接对比这个值即可,保证队列中单调不增

while (tou<=wei&&f[q[wei]]-q[wei]<f[i-x]-(i-x)) wei--;


队首:判断队首的点是否在决策区间内

因为队列是单调不增的,所以队首状态就一定是最优的了:f[i]=max(f[i],f[q.front]+i−q.front)f[i]=max(f[i],f[q.front]+i−q.front)

最后,判断是否f[n]f[n]是否满足90%的条件即可

tip

注意f[0]f[0]也是一个转移状态

insert的时候++sz写错了。。。mmp

#include<cstdio>
#include<cstring>
#include<iostream>
#include<queue>

using namespace std;

const int N=1100003;

int n,m,ch[N<<1][2],dis[N<<1],fa[N<<1],last=1,root=1,sz=1,len;
int L
,f
,q[N<<1];
char s
;

void insert(int x)
{
int now=++sz,pre=last;
last=now;
dis[now]=dis[pre]+1;
for (;pre&&!ch[pre][x];pre=fa[pre]) ch[pre][x]=now;
if (!pre) fa[now]=root;
else {
int q=ch[pre][x];
if (dis[q]==dis[pre]+1) fa[now]=q;
else
{
int nows=++sz;
dis[nows]=dis[pre]+1;
memcpy(ch[nows],ch[q],sizeof(ch[q]));
fa[nows]=fa[q]; fa[q]=fa[now]=nows;
for (;pre&&ch[pre][x]==q;pre=fa[pre]) ch[pre][x]=nows;
}
}
}

void get()
{
int now=root,sum=0;
for (int i=1;i<=len;i++)    //在SAM上匹配
{
int x=s[i]-'0';
if (ch[now][x]!=0)
now=ch[now][x],sum++;
else
{
while (now&&!ch[now][x]) now=fa[now];
if (!now) now=root,sum=0;
else sum=dis[now]+1,now=ch[now][x];
}
L[i]=sum;
}
}

bool check(int x)
{
int tou=1,wei=0;
for (int i=1;i<=len;i++)
{
f[i]=f[i-1];
if (i-x<0) continue;     //此状态没有转移意义
while (tou<=wei&&f[q[wei]]-q[wei]<f[i-x]-i+x) wei--;
q[++wei]=i-x;
while (tou<=wei&&q[tou]<i-L[i]) tou++;   //决策区间
if (tou<=wei) f[i]=max(f[i],f[q[tou]]+i-q[tou]);
}
return f[len]*10>=len*9;
}

int solve()
{
get();
int l=0,r=len,ans=0;
while (l<=r)
{
int mid=(l+r)>>1;
if (check(mid)) ans=max(ans,mid),l=mid+1;
else r=mid-1;
}
return ans;
}

int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=m;i++)
{
scanf("%s",s+1);
last=root; len=strlen(s+1);
for (int j=1;j<=len;j++) insert(s[j]-'0');
}
for (int i=1;i<=n;i++)
{
scanf("%s",s+1);
len=strlen(s+1);
printf("%d\n",solve());
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: