[BJOI2020] 封印
一、题目
二、解法
今天不知道为什么手感这么好,写一发完全没调就过掉了。
我感觉这种多组询问的字符串题是很难的,经常没有什么思路。我先考虑了一下能不能像 区间本质不同的子串个数 这样直接离线,但我想了很久发现不行的。
正确的做法是很天马行空的,我们 考虑预处理出 \(s\) 的所有子串在 \(t\) 中有没有出现 ,枚举是不可能枚举的,要把他们压在一起快速的处理。设 \(R[i]\) 为 \([i,R[i]]\) 在 \(t\) 中出现过,那么对于比 \(R[i]\) 小的右端点是一定出现过的,比 \(R[i]\) 大的右端点是没有出现过的。
\(R[i]\) 用后缀自动机可以快速处理,因为 \([i,R[i]]\) 是在 \(t\) 中出现过的,\([i+1,R[i]]\) 也是在 \(t\) 中出现过的,那么 \(R[i+1]\geq R[i]\),所以说我们可以暴力跳转移,当发现长度不适应后缀自动机上的这个点时,就可以跳后缀树上的父亲。不难发现时间复杂度是 \(O(n)\) 的。
知道了 \(R[i]\) 之后,询问 \((l,r)\) 的答案很容易写出来:
\[\max_{i=l}^r (\min(R[i],r)-i+1)
\]
这个式子乍看上去没有办法优化,但别忘了我们还有一个法宝:离线 。如果你觉得里面的 \(\min\) 特别恶心那么我们可以分类讨论来去掉这个 \(\min\) :
- \(R[i]\leq r\),那么里面的柿子就变成了:\(R[i]-i+1\)
- \(R[i]>r\),那么里面的柿子就变成了:\(r-i+1\)
拆掉 \(\min\) 之后问题变成了二维偏序之类的东西,解决他的固定套路就是 排序降维 。那么我们把 \(R[i],r\) 都从小到大排序,然后维护两颗线段树,一颗维护 \(-i\) 的最大值,一颗维护 \(R[i]-i+1\) 就可以了。
时间复杂度 \(O(n\log n)\)
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int M = 200005;
const int inf = -1e9;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,k,cnt,last,R[M],ans[M];char s[M],t[M];
int mx[2][4*M];
struct node
{
int fa,len,ch[2];
}a[2*M];
struct reg//regret
{
int l,r,id;
bool operator < (const reg &R) const
{
return r<R.r;
}
}b[M],q[M];
void add(int c)
{
int p=last,np=last=++cnt;
a[np].len=a[p].len;
for(;p && !a[p].ch[c];p=a[p].fa) a[p].ch[c]=np;
if(!p) a[np].fa=1;
else
{
int q=a[p].ch[c];
if(a[p].len+1==a[q].len) a[np].fa=q;
else
{
int nq=++cnt;a[nq]=a[q];
a[nq].len=a[p].len+1;
a[np].fa=a[q].fa=nq;
for(;p && a[p].ch[c]==q;p=a[p].fa) a[p].ch[c]=nq;
}
}
}
void ins(int i,int l,int r,int id,int v,int f)
{
if(l==r)
{
mx[f][i]=v;
return ;
}
int mid=(l+r)>>1;
if(mid>=id) ins(i<<1,l,mid,id,v,f);
else ins(i<<1|1,mid+1,r,id,v,f);
mx[f][i]=max(mx[f][i<<1],mx[f][i<<1|1]);
}
int ask(int i,int l,int r,int L,int R,int f)
{
if(L>r || l>R) return inf;
if(L<=l && r<=R) return mx[f][i];
int mid=(l+r)>>1;
return max(ask(i<<1,l,mid,L,R,f),ask(i<<1|1,mid+1,r,L,R,f));
}
signed main()
{
scanf("%s %s",s+1,t+1);
n=strlen(s+1);m=strlen(t+1);
cnt=last=1;//attention
for(int i=1;i<=m;i++)
add(t[i]-'a');
for(int i=1,p=1;i<=n;i++)
{
int r=min(i-1,R[i-1]);
if(r==i-1) p=1;
while(p!=1 && a[a[p].fa].len>r-i+1) p=a[p].fa;
while(r<n && a[p].ch[s[r+1]-'a'])
{
r++;
p=a[p].ch[s[r]-'a'];
}
R[i]=r;
b[i]=reg{i,r,0};
}
sort(b+1,b+1+n);
k=read();
for(int i=1;i<=k;i++)
{
int l=read(),r=read();
q[i]=reg{l,r,i};
}
sort(q+1,q+1+k);
memset(mx,-0x3f,sizeof mx);
for(int i=1;i<=n;i++)
ins(1,1,n,i,-i,0);
for(int i=1,j=1;i<=k;i++)
{
int l=q[i].l,r=q[i].r,id=q[i].id;
while(j<=n && b[j].r<=r)
{
ins(1,1,n,b[j].l,inf,0);
ins(1,1,n,b[j].l,b[j].r-b[j].l+1,1);
j++;
}
ans[id]=max(ask(1,1,n,l,r,1),r+ask(1,1,n,l,r,0)+1);
ans[id]=max(0,ans[id]);
}
for(int i=1;i<=k;i++)
printf("%d\n",ans[i]);
}