P2336-[SCOI2012]喵星球上的点名【SA,树状数组】

正题

题目链接:https://www.luogu.com.cn/problem/P2336


题目大意

n n n个名字(每个名字两个串), m m m次点名,如果一次点名里是一个名字两个串中的子串该人就要答到。

对于每次点名求多少个人答到,每个名字求答到多少次。


解题思路

先考虑第一问,我们将所有串串在一起(中间加大数)然后跑出 S A SA SA和所有的 L C P LCP LCP

然后对于每个点名串起始位置 k k k我们寻找一个区间 [ l , r ] [l,r] [l,r]使得 L C P ( k , i ) ≥ l e n ( i ∈ [ l , r ] ) LCP(k,i)\geq len(i\in[l,r]) LCP(k,i)len(i[l,r])

这个区间我们求出 h e i g h t height height后用 S T ST ST表+二分可以求出

这样如果任意一个名字串的在这个区间内就会被点到。因为有名有姓,我们把同一个名字的 S A SA SA染成同一种颜色,这样问题就变为了在 [ l , r ] [l,r] [l,r]这个区间内有多少种颜色

而第二问也变为了有多少个区间包含该种颜色,我们直接用树状数组做就好了。

时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)


c o d e code code

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#define lowbit(x) (x&-x)
using namespace std;
const int N=5e5+10;
struct node{
	int l,r,id;
}q[N];
int n,m,num,Q,s[N];
int col[N],len[N],head[N];
int sa[N],c[N],x[N],y[N];
int rk[N],height[N];
int st[N][20],lg[N];
int pre[N],last[N],qq[N];
int ans1[N],ans2[N];
struct Tree_Array{
	int t[N];
	void Change(int x,int val){
		if(!x) return;
		while(x<=n){
			t[x]+=val;
			x+=lowbit(x);
		}
		return;
	}
	int Ask(int x){
		int ans=0;
		while(x){
			ans+=t[x];
			x-=lowbit(x);
		}
		return ans;
	}
}T1,T2;
void Init(){
	scanf("%d%d",&num,&Q);
	m=1e4;
	for(int i=1;i<=num;i++){
		for(int j=0;j<2;j++){
			int x,c;
			scanf("%d",&x);
			while(x--){
				scanf("%d",&c);
				s[++n]=c;col[n]=i;
			}
			s[++n]=++m;
		}
	}
	for(int i=1;i<=Q;i++){
		int x;head[n+1]=i;
		scanf("%d",&len[i]);
		for(int j=1;j<=len[i];j++){
			scanf("%d",&x);
			s[++n]=x;col[n]=-i;
		}
		s[++n]=++m;
	}
}
void Qsort(){
	for(int i=1;i<=m;i++)c[i]=0;
	for(int i=1;i<=n;i++)c[x[i]]++;
	for(int i=1;i<=m;i++)c[i]+=c[i-1];
	for(int i=n;i>=1;i--)sa[c[x[y[i]]]--]=y[i],y[i]=0;
	return;
}
void Get_SA(){
	for(int i=1;i<=n;i++)
		x[i]=s[i],y[i]=i;
	Qsort();
	for(int w=1;w<=n;w<<=1){
		int p=0;
		for(int i=n-w+1;i<=n;i++) y[++p]=i;
		for(int i=1;i<=n;i++)
			if(sa[i]>w) y[++p]=sa[i]-w;
		Qsort();swap(x,y);x[sa[1]]=p=1;
		for(int i=2;i<=n;i++)
			x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+w]==y[sa[i-1]+w])?p:++p;
		if(p==n) break;m=p;
	}
	return;
}
void Get_Height(){
	int k=0;
	for(int i=1;i<=n;i++)rk[sa[i]]=i;
	for(int i=1;i<=n;i++){
		//if(rk[i]==1) continue;
		if(k) k--;int j=sa[rk[i]-1];
		while(i+k<=n&&j+k<=n&&s[i+k]==s[j+k]) k++;
		height[rk[i]]=k;
	}
	return;
}
void Get_ST(){
	lg[0]=-1;
	for(int i=1;i<=n;i++)
		st[i][0]=height[i],lg[i]=lg[i>>1]+1;
	for(int j=1;j<19;j++)
		for(int i=1;i+(1<<j-1)<=n;i++)
			st[i][j]=min(st[i][j-1],st[i+(1<<j-1)][j-1]);
	return;
}
int LCP(int l,int r){
	if(l==r) return 1e9+7;
	if(l>r) swap(l,r);l++;
	int z=lg[r-l+1];
	return min(st[l][z],st[r+1-(1<<z)][z]);
}
bool cmp(node x,node y)
{return x.r<y.r;}
void Get_Pre(){
	for(int i=1;i<=n;i++){
		if(col[sa[i]]>0){
			pre[i]=last[col[sa[i]]];
			last[col[sa[i]]]=i;
		}
		if(head[i]){
			int l=1,r=rk[i];
			while(l<=r){
				int mid=(l+r)>>1;
				if(LCP(mid,rk[i])>=len[head[i]]) r=mid-1;
				else l=mid+1;
			}
			q[head[i]].l=l;
			l=rk[i];r=n;
			while(l<=r){
				int mid=(l+r)>>1;
				if(LCP(rk[i],mid)>=len[head[i]]) l=mid+1;
				else r=mid-1;
			}
			q[head[i]].r=r;q[head[i]].id=head[i];
			qq[q[head[i]].l]++;
		}
	}
	sort(q+1,q+1+Q,cmp);
}
void Get_Ans(){
	int h=1,z=1;
	for(int i=1;i<=n;i++){
		T2.Change(i,qq[i]);
		if(col[sa[i]]>0){
			T1.Change(pre[i],-1);T1.Change(i,1);
			ans2[col[sa[i]]]+=T2.Ask(i)-T2.Ask(pre[i]);
		}
		while(h<=Q&&q[h].r<=i){
			ans1[q[h].id]+=T1.Ask(q[h].r)-T1.Ask(q[h].l-1);
			T2.Change(q[h].l,-1);h++;
		}
	}
	return;
}
int main()
{
	Init();
	Get_SA();
	Get_Height();
	Get_ST();
	Get_Pre();
	Get_Ans();
	for(int i=1;i<=Q;i++)
		printf("%d\n",ans1[i]);
	for(int i=1;i<=num;i++)
		printf("%d ",ans2[i]);
}
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值