好题推荐(KMP+DFS序+CDQ分治/主席树)

题目描述

给一个字符串 S S S​,有 Q Q Q​次询问,每次询问给两个数 x , y x,y x,y​,求 S S S​的前缀 S [ 1 − > x ] S[1->x] S[1>x]​和 S S S​的后缀 S [ ( n − y + 1 ) − > n ] S[(n-y+1)->n] S[(ny+1)>n]​这两个字符串串联后得到的新串 T = S [ 1 − x ] + S [ ( n − y + 1 ) − > n ] T=S[1-x]+S[(n-y+1)->n] T=S[1x]+S[(ny+1)>n]​,在 S S S​中出现了多少次。

考试思路

昨天lsj大佬说今天有一道很好的字符串题
看完题面之后发现只有一道字符串
如果不是联赛,我肯定会往SAM\SA方向考虑
之前做过一个题,求一个字符串的每个前缀的出现次数
那个题是KMP,所以就先写了个KMP的模板
那个题的结论是对于一个前缀 i i i,所有i出现的位置
前缀 N e x t [ i ] Next[i] Next[i]都会出现,且不重复,证明类似于SAM
换句或说,一个前缀i的出现次数就是以i为Next的其他前缀的出现次数之和
然后考试时想到这就不会了,用个bitset存起来每个前缀和后缀出现的位置

PS:

考试时由于少上传了一组数据, n 2 n^2 n2的做法也能A,后来又重评了

正解

Step 1

其实就是我上面的那个结论
只不过这里把这个东西实体化了
可以发现,如果从i向Next[i]连一条边
那么会构成一棵树
并且对于一个节点,他的字数大小就是他的出现次数
于是我们就正反各建一棵树
注意,为了匹配方便,我们将后缀的树的节点编号都减一,这样对于一个位置,在两棵树上编号就一样了

Step 2

考虑有多少个位置符合条件
在前缀树上,肯定是x的子树节点之一
在后缀树上,肯定是y-1的子树节点之一
(为什么减一刚才说过了)
那么符合条件的节点就是既是x的子树中,有时y-1的子树中
把两棵树的Dfs序算出来
则j节点P符合要求的条件为
s t [ x ] ≤ d f n [ P ] ≤ e d [ x ] st[x]\leq dfn[P]\leq ed[x] st[x]dfn[P]ed[x] 这是前缀树的DFS序
s t [ y − 1 ] ≤ d f n [ p − 1 ] ≤ e d [ y − 1 ] st[y-1]\leq dfn[p-1]\leq ed[y-1] st[y1]dfn[p1]ed[y1]这是后缀的DFS序
对于每个节点
如果把它在前缀树上的DFS序设为x,后缀树上的DFS设为y
那么每个节点都可以表示为二维平面上的一个点(x,y)
那么条件进一步转化为
求横坐标在 s t [ x ] st[x] st[x] e d [ x ] ed[x] ed[x]之间
纵坐标在 s t [ y − 1 ] st[y-1] st[y1] e d [ y − 1 ] ed[y-1] ed[y1]之间的点的个数

Step 3

上一部分的最终结论是一个经典的二维数点问题
这是模板题[SHOI2007]园丁的烦恼
其实可以转化为三维偏序或二维偏序
针对二维偏序
主流的大致做法有四种
1:CDQ分治(离线)
2:树状数组离散化(离线)
3:主席树(在线)
4:扫描线(离线)
主席树和扫描线细节比较多,而且想练一练CDQ分治,所以就选择了CDQ分治

#include<bits/stdc++.h>
using namespace std;
const int N = 2e5+7;
struct node
{
	int x,y,id,t;
}q[5*N],tmp[5*N];
struct edge
{
	int next,y;	
};
struct tree
{
	edge e[2*N];
	int link[N];
	int t=0;
	int st[N],ed[N];
	int cnt=0; 
	void clear()
	{
		memset(e,0,sizeof(e));
		memset(link,0,sizeof(link));
		t=0;
		memset(st,0,sizeof(st));
		memset(ed,0,sizeof(ed));
		cnt=0;
	}
}auf,suf;
void add_auf(int x,int y)
{
	auf.e[++auf.t].y=y;
	auf.e[auf.t].next=auf.link[x];
	auf.link[x]=auf.t;
}
void add_suf(int x,int y)
{
	suf.e[++suf.t].y=y;
	suf.e[suf.t].next=suf.link[x];
	suf.link[x]=suf.t;
}
int tot=0,ans[N];
inline int read()
{
	int X=0; bool flag=1; char ch=getchar();
	while(ch<'0'||ch>'9') {if(ch=='-') flag=0; ch=getchar();}
	while(ch>='0'&&ch<='9') {X=(X<<1)+(X<<3)+ch-'0'; ch=getchar();}
	if(flag) return X;
	return ~(X-1);
}
bool cmp(node a,node b)
{
	if(a.x==b.x)
	{
		if(a.y==b.y) return a.t<b.t;
		return a.y<b.y;
	}
	return a.x<b.x;
}
int n,m;
void Add(int x,int y,int t,int id)
{
	q[++tot].id=id;
	q[tot].x=x;
	q[tot].y=y;
	q[tot].t=t;
}
void CDQ(int l,int r)
{
	if(l>=r) return;
	int mid=(l+r)>>1;
	CDQ(l,mid);
	CDQ(mid+1,r);
	int i=l,j=mid+1,len=0,top=0;
	while(i<=mid&&j<=r)
	{
		if(q[i].y<=q[j].y)
		{
			if(q[i].t==0) len++;
			tmp[++top]=q[i++];	
		}	
		else
		{
			if(q[j].t==2) ans[q[j].id]+=len;
			if(q[j].t==1) ans[q[j].id]-=len;
			tmp[++top]=q[j++];
		}
	}	
	while(i<=mid) 
	tmp[++top]=q[i++];
	while(j<=r)
	{
		if(q[j].t==2) ans[q[j].id]+=len;
		if(q[j].t==1) ans[q[j].id]-=len;
		tmp[++top]=q[j++];		
	}
	for(int i=1;i<=top;i++)
	q[l+i-1]=tmp[i]; 
}
char a[N];
int Next[N],Mext[N];
void KMP()
{
	Next[1]=0;
	for(int i=2,j=0;i<=n;i++)
	{
		while(j>0&&a[i]!=a[j+1]) j=Next[j];
		if(a[i]==a[j+1]) j++;
		Next[i]=j;
	}
	Mext[n]=n+1;
	for(int i=n-1,j=n+1;i>=1;i--)
	{
		while(j<n+1&&a[i]!=a[j-1]) j=Mext[j];
		if(a[i]==a[j-1]) j--;
		Mext[i]=j;
	}	
} 
void dfs_auf(int x)
{
	auf.st[x]=++auf.cnt;
	for(int i=auf.link[x];i;i=auf.e[i].next)
	{
		int y=auf.e[i].y;
		dfs_auf(y);
	}
	auf.ed[x]=auf.cnt;
}
void dfs_suf(int x)
{
	suf.st[x]=++suf.cnt;
	for(int i=suf.link[x];i;i=suf.e[i].next)
	{
		int y=suf.e[i].y;
		dfs_suf(y);
	}
	suf.ed[x]=suf.cnt;
}
void Build()
{
	for(int i=1;i<=n;i++)
	{
		add_auf(Next[i],i);
		add_suf(Mext[i]-1,i-1);
	}
	dfs_auf(0);
	dfs_suf(n);
}
int Q;
void clear()
{
	memset(Next,0,sizeof(Next));
	memset(Mext,0,sizeof(Mext)); 
	memset(q,0,sizeof(q));	
	auf.clear();
	suf.clear();
	memset(ans,0,sizeof(ans));
	tot=0;
}
void solve()
{
	clear();
	scanf("%d%d",&n,&Q);
	scanf("%s",a+1);
	KMP();
	Build();
	for(int i=0;i<=n;i++)
	Add(auf.st[i],suf.st[i],0,0);
	for(int i=1;i<=Q;i++)
	{
		int a,b,c,d;
		int x=read(),y=read();
		y=n-y;
		a=auf.st[x];
		c=auf.ed[x];
		b=suf.st[y];
		d=suf.ed[y];
		Add(c,d,2,i);
		Add(a-1,b-1,2,i);
		Add(a-1,d,1,i);
		Add(c,b-1,1,i);
	}
	sort(q+1,q+tot+1,cmp);
	CDQ(1,tot); 
	for(int i=1;i<=Q;i++)
	printf("%d\n",ans[i]);	
}
int main()
{
	freopen("e.in","r",stdin);
	freopen("e.out","w",stdout);
	int T;
	cin>>T;
	while(T--)
	{
		solve(); 
	}
	return 0;
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值