[冲刺国赛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();
}
posted @ 2022-07-04 16:57  C202044zxy  阅读(76)  评论(0编辑  收藏  举报