洛谷2336 BZOJ2754 SCOI2012 喵星球上的点名 SA 莫队 二分

本文介绍了一种使用后缀数组(SA)算法解决大规模字符串匹配问题的方法。通过将所有字符串连接并插入特殊字符,利用SA算法求出rank和height,实现高效查询字符串是否为某一串的子串。文章详细解释了如何通过二分查找确定查询串的前缀范围,以及如何处理重复计数问题,最终给出O(nlogn+nn)的时间复杂度解决方案。

题目链接

题意:
n n n个人,每个人有两个串,一个表示姓,一个表示名,这里用数字表示字符。有 m m m次询问,对于每次询问,你要回答有多少个人的姓或者名至少有一个是给出的串的子串。最后再对于这 n n n个人中的每一个人,回答这个 m m m个串中的多少个串是这个人的姓或者名至少一个的子串。 n &lt; = 5 e 4 , m &lt; = 1 e 5 , 总 长 度 &lt; = 1 e 5 , 数 值 &lt; = 1 e 4 n&lt;=5e4,m&lt;=1e5,总长度&lt;=1e5,数值&lt;=1e4 n<=5e4,m<=1e5,<=1e5,<=1e4

题解:
不会这个题,是看了题解的。(虽然我几乎没有什么不是看题解的题,但是这个题我似乎都不知道该怎么讲是怎么想到要用SA的。。)

我们先把所有串首尾相连,中间用一个大权值的字符隔开。然后对这个串做一次SA,并且求出rank、height。所有都连起来做SA的好处是一个串的所有子串都会成为它某一个后缀的前缀,那么一个询问串是否是一个串的子串,只需要判断是不是某一个后缀的前缀就可以了。

我们对于一个询问,会包含它的串应该是所有前缀包含它的后缀所在的那些串,我们可以对rank向前向后二分,分别求出一个以这个询问串为前缀的左端点和右端点。这样我们每次询问就是问这个左端点与右端点之间有多少个串出现,并且让这些串答案加一。

主要是有一个问题比较麻烦,就是如果这个询问串既是这个人的姓的子串,又是这个人名的子串,这时候不能算两遍。这个题不是强制在线的,于是我们可以离线下来,用一个莫队来处理。每次一个后缀加入的时候,我们判断这个后缀所在的串是否已经加入过了,然后看是否要让答案加一,对于不在区间内也是同理。对于第二问,我们的做法是,在这个串加入的时候把所有后面还没有算过的操作数加进来,当执行到让它退出这个区间的操作的时候,我们再减去这时候还没有的操作总数,那么这个差就是这一部分操作对这个串的贡献。

莫队这块要是说得不是清楚的话可以看看代码,代码还是比较清楚易懂的。

这样就做完这个题了,复杂度 O ( n l o g n + n n ) O(nlogn+n\sqrt{n}) O(nlogn+nn )

代码:

#include <bits/stdc++.h>
using namespace std;

int n,m,s[400010],rk[400010],sa[400010],he[400010][22],ji[200010],len[200010];
int cnt[400010],pos[400010],a[400010],b[400010],c[400010],ans1,num[400010],ans[400010];
struct node
{
	int l,r,id;
}q[400010];
inline int read()
{
	int x=0;
	char s=getchar();
	while(s>'9'||s<'0')
	s=getchar();
	while(s>='0'&&s<='9')
	{
		x=x*10+s-'0';
		s=getchar();
	}
	return x;
}
inline void get_sa()
{
	for(int i=1;i<=n;++i)
	rk[i]=a[i];
	for(int i=1;i<=n;++i)
	s[rk[i]]++;
	for(int i=1;i<=10003;++i)
	s[i]+=s[i-1];
	for(int i=n;i>=1;--i)
	sa[s[rk[i]]--]=i;	
	int p=0,ln=1,x=10003;
	while(p<n)
	{
		int k=0;
		for(int i=n-ln+1;i<=n;++i)
		b[++k]=i;
		for(int i=1;i<=n;++i)
		{
			if(sa[i]>ln)
			b[++k]=sa[i]-ln;
		}
		for(int i=1;i<=n;++i)
		c[i]=rk[b[i]];
		memset(s,0,sizeof(s));
		for(int i=1;i<=n;++i)
		s[c[i]]++;
		for(int i=1;i<=x;++i)
		s[i]+=s[i-1];
		for(int i=n;i>=1;--i)
		sa[s[c[i]]--]=b[i];		
		for(int i=1;i<=n;++i)
		c[i]=rk[i];
		p=1;
		rk[sa[1]]=1;
		for(int i=2;i<=n;++i)
		{
			if(!(c[sa[i]]==c[sa[i-1]]&&c[sa[i]+ln]==c[sa[i-1]+ln]))
			++p;
			rk[sa[i]]=p;
		}
		x=p;
		ln*=2;
	}
	x=0;
	for(int i=1;i<=n;++i)
	{
		if(rk[i]==1)
		continue;
		if(x)
		--x;
		int j=sa[rk[i]-1];
		while(a[i+x]==a[j+x])
		++x;	
		he[rk[i]][0]=x;
	}
	for(int j=1;(1<<j)<=n;++j)
	{
		for(int i=1;i<=n-(1<<j)+1;++i)
		he[i][j]=min(he[i][j-1],he[i+(1<<j-1)][j-1]);		
	}
}
inline int lcp(int x,int y)
{
	int z=log2(y-x);
	return min(he[x+1][z],he[y-(1<<z)+1][z]);
}
inline int cmp(node x,node y)
{
	if(pos[x.l]!=pos[y.l])
	return pos[x.l]<pos[y.l];
	return x.r<y.r;
}
inline void ins(int x,int y)
{
	if(!s[c[x]])
	{
		++ans1;
		num[c[x]]+=m-y+1;
	}
	++s[c[x]];
}
inline void del(int x,int y)
{
	--s[c[x]];
	if(!s[c[x]])
	{
		--ans1;
		num[c[x]]-=m-y+1;
	}
}
int main()
{
	for(int i=1;i<=400000;++i)
	a[i]=1;
	n=read();
	m=read();	
	int qwq=0,gg=n;
	for(int i=1;i<=n;++i)
	{
		int l=read();
		for(int j=1;j<=l;++j)
		{
			int x=read()+1;
			a[++qwq]=x;
		}
		a[++qwq]=10002;
		l=read();
		for(int j=1;j<=l;++j)
		{
			int x=read()+1;
			a[++qwq]=x;
		}
		a[++qwq]=10003;
	}
	int jilu=qwq;
	for(int i=1;i<=m;++i)
	{
		len[i]=read();
		ji[i]=qwq+1;
		for(int j=1;j<=len[i];++j)
		{
			int x=read()+1;
			a[++qwq]=x;
		}
		if(i!=m)
		a[++qwq]=10003;
	}	
	n=qwq;
	get_sa();
	int sz=sqrt(n);
	for(int i=1;i<=n;++i)
	pos[i]=i/sz+1;
	memset(c,0,sizeof(c));
	int cnt=1;
	for(int i=1;i<=jilu;++i)
	{
		if(a[i]==10003)
		++cnt;
		else
		c[rk[i]]=cnt;
	}
	for(int i=1;i<=m;++i)
	{
		int x=rk[ji[i]];
		int l=1,r=x-1,mid,res=x,jil,jir;
		while(l<=r)
		{
			mid=(l+r)>>1;
			if(lcp(mid,x)>=len[i])
			{
				res=mid;
				r=mid-1;
			}
			else
			l=mid+1;
		}
		jil=res;
		l=x+1;
		r=n;
		res=x;		
		while(l<=r)
		{
			mid=(l+r)>>1;
			if(lcp(x,mid)>=len[i])
			{
				res=mid;
				l=mid+1;
			}
			else
			r=mid-1;
		}
		jir=res;
		q[i].l=jil;
		q[i].r=jir;
		q[i].id=i;
	}
	sort(q+1,q+m+1,cmp);
	memset(s,0,sizeof(s));
	int l=1,r=0;
	for(int i=1;i<=m;++i)
	{
		while(r<q[i].r)
		ins(++r,i);
		while(r>q[i].r)
		del(r--,i);
		while(l<q[i].l)
		del(l++,i);
		while(l>q[i].l)
		ins(--l,i);
		ans[q[i].id]=ans1;
		if(s[0])
		--ans[q[i].id];
	}
	for(int i=1;i<=m;++i)
	printf("%d\n",ans[i]);
	for(int i=1;i<=gg;++i)
	printf("%d ",num[i]);
	printf("\n");
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值