P4022 [CTSC2012] 熟悉的文章 题解
P4022 [CTSC2012] 熟悉的文章
容易发现,能和 SAM 搞在一起的东西还挺多的。
首先要求最大的 \(L\),显然这个 \(L\) 是满足单调性的:若 \(L\) 合法,则 \(L-1,L-2,\dots\) 都合法。所以考虑二分这个 \(L\)。
首先看到这个序列分段,应该想到经典的 DP:设 \(f_i\) 表示到第 \(i\) 个位置最长能匹配多长的字符串,那么显然有转移方程
\[f_i=\max\{f_j+i-j\}~,\quad j\in[i-w_i,i-L]
\]
其中 \(w_i\) 是以 \(i\) 为结尾的字符串出现在模板串中的最长长度。用 SAM 处理这个东西应该是基操,考虑到题目中给出了多个模板串,所以套广义 SAM 的板子就可以处理出它。
现在复杂度是 \(O(n^2)\) 的,但是注意到随着 \(i\) 的增加,\(j\) 的合法取值范围是一个滑动窗口,可以采用单调队列优化,复杂度变为 \(O(n)\)。
#include<bits/stdc++.h>
using namespace std;
constexpr int MAXN=2.2e6+5;
int N,M,n,w[MAXN];
struct{
int tot=1;
struct Trie{
int fa,c,s[2];
}t[MAXN];
void ins(const string&s){
int p=1;
for(int c:s){
c-='0';
if(!t[p].s[c]){
t[p].s[c]=++tot;
t[tot].fa=p,t[tot].c=c;
}
p=t[p].s[c];
}
}
}T;
struct{
int tot=1;
struct SAM{
int len,fa,s[2];
}sam[MAXN];
int ins(int x,int lst){
sam[++tot].len=sam[lst].len+1;
int pos=lst,ch=T.t[x].c;
lst=tot;
while(pos&&!sam[pos].s[ch]){
sam[pos].s[ch]=tot;
pos=sam[pos].fa;
}
if(!pos) sam[tot].fa=1;
else{
int p=pos,q=sam[pos].s[ch];
if(sam[p].len+1==sam[q].len) sam[tot].fa=q;
else{
sam[++tot]=sam[q];
sam[tot].len=sam[p].len+1;
sam[q].fa=sam[lst].fa=tot;
while(pos&&sam[pos].s[ch]==q){
sam[pos].s[ch]=tot;
pos=sam[pos].fa;
}
}
}
return lst;
}
void build(){
queue<pair<int,int>>q;
for(int i=0;i<2;i++) if(T.t[1].s[i]) q.emplace(T.t[1].s[i],1);
while(!q.empty()){
int u=q.front().first,lst=q.front().second;
q.pop();
int now=ins(u,lst);
for(int i=0;i<2;i++) if(T.t[u].s[i]) q.emplace(T.t[u].s[i],now);
}
}
void initw(const string&s){
int lst=0,pos=1;
for(int i=1;i<=n;i++){
int c=s[i]-'0';
if(sam[pos].s[c]) lst++,pos=sam[pos].s[c];
else{
while(pos&&!sam[pos].s[c]) pos=sam[pos].fa;
if(!pos) lst=0,pos=1;
else lst=sam[pos].len+1,pos=sam[pos].s[c];
}
w[i]=lst;
}
}
int q[MAXN],f[MAXN];
bool check(int x){
int h=1,t=0;
memset(f,0,x<<2);
for(int i=x;i<=n;i++){
f[i]=f[i-1];
while(h<=t&&f[q[t]]-q[t]<=f[i-x]-i+x) t--;
q[++t]=i-x;
while(h<=t&&q[h]<i-w[i]) h++;
if(h<=t) f[i]=max(f[i],f[q[h]]+i-q[h]);
}
return n*0.9<=f[n];
}
}S;
int main(){
cin.tie(nullptr)->sync_with_stdio(0);
cin>>N>>M;
for(int i=1;i<=M;i++){
string s;
cin>>s;
T.ins(s);
}
S.build();
for(int i=1;i<=N;i++){
string s;
cin>>s;
n=s.size();
s=' '+s;
S.initw(s);
int l=0,r=n,ans=0;
while(l<=r){
int mid=(l+r)>>1;
if(S.check(mid)) ans=mid,l=mid+1;
else r=mid-1;
}
cout<<ans<<'\n';
}
return 0;
}
即得易见平凡,仿照上例显然。留作习题答案略,读者自证不难。
反之亦然同理,推论自然成立。略去过程 $\rm QED$,由上可知证毕。

浙公网安备 33010602011771号