[冲刺国赛2022] 字符串
一、题目
给定一个长度为 \(n\) 的字符串 \(S\),有 \(m\) 次询问 \((x,y)\),问这个串长度为 \(x\) 的前缀和长度为 \(y\) 的后缀连接成的新串 \(T\) 在 \(S\) 中的出现次数。
\(n,m\leq 2\cdot 10^5\)
二、解法
考虑子串 \(S[l,r]\) 对询问 \((x,y)\) 产生贡献的充要条件是:\(S[1,x]=S[l,l+x-1]\and S[n-y+1,n]=S[r-y+1,r]\)
那么可以把两个条件拆开,只需要加上 \(l+x=r-y+1\) 的限制即可。拆开后发现两边都是 \(border\) 的形式,所以可以对正串和反串都做一次 \(\tt kmp\),把 \(i\) 的 \(border\) 设置为 \(i\) 的父亲,那么 \(x\) 的子树内就是它所有可能的出现位置。
问题可以转化成:问 \(y\) 子树内有多少个点 \(z\),使得 \(z\) 的前一个点在 \(x\) 子树中,可以扫描线 + 树状数组解决。
时间复杂度 \(O(n\log n)\)
三、总结
分析字符串问题时,要列出具体的等式关系。
#include <cstdio>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
const int M = 200005;
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 T,n,m,k,nxt[M],b[M];char s[M];
int ans[M],i1[M],o1[M],i2[M],o2[M],id[M];
vector<int> g1[M],g2[M];
struct node{int l,r,id;};vector<node> A[M],B[M];
void kmp()
{
for(int i=2,j=0;i<=n;i++)
{
while(j && s[j+1]^s[i]) j=nxt[j];
if(s[j+1]==s[i]) j++;
nxt[i]=j;
}
}
void dfs1(int u)
{
if(u>0) i1[u]=++k;
for(int v:g1[u]) dfs1(v);
if(u>0) o1[u]=k;
}
void dfs2(int u)
{
if(u<=n) i2[u]=++k;
for(int v:g2[u]) dfs2(v);
if(u<=n) o2[u]=k;
}
void add(int x)
{
for(int i=x;i<=n;i+=i&(-i)) b[i]++;
}
int ask(int x)
{
int r=0;
for(int i=x;i>0;i-=i&(-i)) r+=b[i];
return r;
}
int ask(int l,int r)
{
return ask(r)-ask(l-1);
}
void work()
{
n=read();m=read();scanf("%s",s+1);
for(int i=0;i<=n+1;i++)
g1[i].clear(),g2[i].clear(),
A[i].clear(),B[i].clear();
kmp();
for(int i=1;i<=n;i++)
g1[nxt[i]].push_back(i);
reverse(s+1,s+1+n);
kmp();
for(int i=1;i<=n;i++)
g2[n-nxt[i]+1].push_back(n-i+1);
k=0;dfs1(0);
k=0;dfs2(n+1);
for(int i=1;i<=n;i++) id[i2[i]]=i;
//
for(int i=1;i<=m;i++)
{
int x=read(),y=read();ans[i]=0;
A[i2[n-y+1]-1].push_back({i1[x],o1[x],i});
B[o2[n-y+1]].push_back({i1[x],o1[x],i});
}
for(int i=1;i<=n;i++) b[i]=0;
for(int i=1;i<=n;i++)
{
if(id[i]!=1) add(i1[id[i]-1]);
for(auto [l,r,id]:A[i]) ans[id]-=ask(l,r);
for(auto [l,r,id]:B[i]) ans[id]+=ask(l,r);
}
for(int i=1;i<=m;i++)
printf("%d\n",ans[i]);
}
signed main()
{
freopen("string.in","r",stdin);
freopen("string.out","w",stdout);
T=read();
while(T--) work();
}