洛谷P4022 [CTSC2012]熟悉的文章 后缀自动机+二分+单调队列优化dp
洛谷P4022 [CTSC2012]熟悉的文章
题意
给\(m\)个\(01\)串\(s_1,s_2,\dots,s_n\),\(n\)次询问,每次询问给出一个\(01\)串\(x\),问是否存在一种分割方式将\(x\)分割成若干个子串使这些子串中长度大于等于\(L\)的且是\(s_1,s_2,\dots,s_n\)中任意一个的子串的总长度大于等于\(|x|\cdot 90\%\),问\(L\)最大为多少,若不存在输出\(0\)。
分析
将这\(m\)个串用一个没出现过的字符连接起来建后缀自动机,二分\(L\),\(check\)就是对于\(x\)的每个前缀在自动机上匹配一个最长的后缀,设这个匹配长度为\(l\),若\(l>=L\),则有\(dp[i]=max\{dp[k]+i-k|k\in [i-l,i-L]\}\),考虑优化,要找到区间\([i-l,i-L]\)中\(dp[k]+i-k\)的最大值然后转移,由于\(i\)是固定的,且对于每个前缀其区间左右端点是单调不减的,于是我们可以用单调队列来维护区间\(dp[k]-k\)的最大值,然后转移即可,最后判断\(dp[n]\cdot 10>=n\cdot 9\)来移动二分区间迭代找到答案。
Code
#include<bits/stdc++.h>
#define rep(i,x,n) for(int i=x;i<=n;i++)
#define per(i,n,x) for(int i=n;i>=x;i--)
#define sz(a) int(a.size())
#define rson mid+1,r,p<<1|1
#define pii pair<int,int>
#define lson l,mid,p<<1
#define ll long long
#define pb push_back
#define mp make_pair
#define se second
#define fi first
using namespace std;
const double eps=1e-8;
const int mod=1e9+7;
const int N=3e6+10;
const int M=1e6+10;
const ll inf=1e18;
int T,n,m;
char s[N];
int q[N];
struct SAM{
int last,cnt;int ch[N][3],fa[N],len[N];
int dp[N];
int newnode(){
++cnt;
return cnt;
}
void insert(int c){
int p=last,np=newnode();last=np;len[np]=len[p]+1;
for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=np;
if(!p) fa[np]=1;
else {
int q=ch[p][c];
if(len[q]==len[p]+1) fa[np]=q;
else{
int nq=newnode();len[nq]=len[p]+1;
memcpy(ch[nq],ch[q],sizeof ch[q]);
fa[nq]=fa[q],fa[q]=fa[np]=nq;
for(;ch[p][c]==q;p=fa[p]) ch[p][c]=nq;
}
}
}
void init(){
last=cnt=1;
}
bool solve(int L){
int ln=strlen(s+1);
rep(i,1,ln) dp[i]=0;
int u=1,l=0;
int dl=0,dr=-1;
for(int i=1;i<=ln;i++){
int j=i-L,c=s[i]-'0';
if(j>=0){
while(dl<=dr&&dp[j]-j>dp[q[dr]]-q[dr]) --dr;
q[++dr]=j;
}
while(!ch[u][c]&&u!=1) u=fa[u],l=len[u];
if(ch[u][c]) u=ch[u][c],l++;
if(l>=L){
while(dl<=dr&&q[dl]<i-l) ++dl;
dp[i]=max(dp[i-1],dp[q[dl]]+i-q[dl]);
}else dp[i]=dp[i-1];
}
return dp[ln]*10>=ln*9;
}
}sam;
int main(){
scanf("%d%d",&n,&m);
sam.init();
rep(i,1,m){
scanf("%s",s+1);
int len=strlen(s+1);
for(int j=1;j<=len;j++) sam.insert(s[j]-'0');
sam.insert(2);
}
rep(i,1,n){
scanf("%s",s+1);
int l=1,r=2000000;
while(l<=r){
int mid=l+r>>1;
if(sam.solve(mid)) l=mid+1;
else r=mid-1;
}
printf("%d\n",r);
}
return 0;
}