P2336 [SCOI2012]喵星球上的点名

题意

我们将所有姓名串和点名串拼在一起,中间用原串中不会出现的数隔开,对每个点记录它是哪个串的。

先考虑第一问:

考虑如果点名串\(s1\)是某个姓名串\(s2\)的子串,则\(s1\)必定和\(s2\)的一个后缀\(s2'\)\(lcp\)\(s1\)的长度,即:\(lcp(s1,s2')=len(s1)\)

对于一个点名串,以它作为前缀的后缀在后缀排序后必定是一个区间,我们可以二分得出,设为\([l,r]\),我们接下来就要统计\([l,r]\)内出现的不同的姓名的个数,这是个经典问题,可以用\(HH\)的项链的的方法离线解决。

再考虑第二问:

我们可以算出每个点被多少询问区间覆盖,但这样会计重。

我们从\(1\)\(n\)\(n\)是串长)扫描所有位置,同时维护可以对这个位置产生贡献的区间。具体说就是对于\([l,r]\),我们在扫到\(l\)时它开始产生贡献,我们对\([l,r]\)区间\(+1\),在扫到\(r+1\)时,它不再产生贡献,我们对\([l,r]\)区间\(-1\),这个可以用差分实现。

现在考虑怎么算出当前位置\(i\)真正的贡献,即算出只包含\(i\),而不包含\(i\)所在的原串的其他点的区间。我们找到\(last_{pos_i}\),其中\(pos_i\)表示\(i\)所在原串,\(last_{pos_i}\)表示上一个属于\(pos_i\)这个原串的位置,那么答案就是\(sum_i-sum_{last_{pos_i}}\)

code:

#include<bits/stdc++.h>
using namespace std;
const int maxm=5*1e4+10;
const int maxQ=1e5+10;
const int maxl=1e6+10;
int n,m,Q,num;
int a[maxl],len[maxl],firpos[maxl],pos[maxl],last[maxm],ans1[maxQ],ans2[maxm];
int sa[maxl],rk[maxl],oldrk[maxl],id[maxl],tmpid[maxl],cnt[maxl],lg[maxl];
int height[maxl][20];
struct Query{int l,r,id;}qr[maxQ];
inline void reads(int now)
{
	firpos[now]=n+1;
	scanf("%d",&len[now]);
	for(int i=1;i<=len[now];i++)scanf("%d",&a[++n]),pos[n]=now;
	a[++n]=now+10000;
}
inline bool cmp(Query x,Query y){return x.r<y.r;}
inline bool check(int x,int y,int k){return oldrk[x]==oldrk[y]&&oldrk[x+k]==oldrk[y+k];}
inline void SA_build()
{
	num=500000;
	for(int i=1;i<=n;i++)cnt[rk[i]=a[i]]++;
	for(int i=1;i<=num;i++)cnt[i]+=cnt[i-1];
	for(int i=n;i;i--)sa[cnt[rk[i]]--]=i;
	for(int t=1;t<=n;t<<=1)
	{
		int tot=0;
		for(int i=n-t+1;i<=n;i++)id[++tot]=i;
		for(int i=1;i<=n;i++)if(sa[i]>t)id[++tot]=sa[i]-t;
		tot=0;
		memset(cnt,0,sizeof(cnt));
		for(int i=1;i<=n;i++)cnt[tmpid[i]=rk[id[i]]]++;
		for(int i=1;i<=num;i++)cnt[i]+=cnt[i-1];
		for(int i=n;i;i--)sa[cnt[tmpid[i]]--]=id[i];
		memcpy(oldrk,rk,sizeof(rk));
		for(int i=1;i<=n;i++)rk[sa[i]]=check(sa[i-1],sa[i],t)?tot:++tot;
		num=tot;
	}
	for(int i=1,j=0;i<=n;i++)
	{
		if(j)j--;
		while(a[i+j]==a[sa[rk[i]-1]+j])j++;
		height[rk[i]][0]=j;
	}
	for(int j=1;j<=18;j++)
		for(int i=1;i+(1<<j)-1<=n;i++)
			height[i][j]=min(height[i][j-1],height[i+(1<<(j-1))][j-1]);
}
inline int query(int l,int r)
{
	l++;
	int t=lg[r-l+1];
	return min(height[l][t],height[r-(1<<t)+1][t]);
}
inline int findl(int id)
{
	int l=1,r=rk[firpos[id]]-1,res=rk[firpos[id]];
	while(l<=r)
	{
		int mid=(l+r)>>1;
		if(query(mid,rk[firpos[id]])<len[id])l=mid+1;
		else res=mid,r=mid-1;
	}
	return res;
}
inline int findr(int id)
{
	int l=rk[firpos[id]]+1,r=n,res=rk[firpos[id]];
	while(l<=r)
	{
		int mid=(l+r)>>1;
		if(query(rk[firpos[id]],mid)<len[id])r=mid-1;
		else res=mid,l=mid+1;
	}
	return res;
}
struct Tree_arry
{
	#define lowbit(x) (x&-x)
	int a[maxl];
	inline void clear(){memset(a,0,sizeof(a));}
	inline void add(int x,int k){for(int i=x;i<=n;i+=lowbit(i))a[i]+=k;}
	inline int query(int x){int res=0;for(int i=x;i;i-=lowbit(i))res+=a[i];return res;}
}tr;
int main()
{
	lg[0]=-1;
	for(int i=1;i<=1e6;i++)lg[i]=lg[i>>1]+1;
	scanf("%d%d",&m,&Q);
	for(int i=1;i<=m;i++)reads(2*i-1),reads(2*i);
	for(int i=1;i<=Q;i++)reads(2*m+i);
	SA_build();
	for(int i=1;i<=Q;i++)qr[i].l=findl(2*m+i),qr[i].r=findr(2*m+i),qr[i].id=i;
	sort(qr+1,qr+Q+1,cmp);
	for(int i=1,j=1;i<=Q;i++)
	{
		while(j<=qr[i].r)
		{
			if(pos[sa[j]]<=2*m)
			{
				if(last[(pos[sa[j]]+1)>>1])tr.add(last[(pos[sa[j]]+1)>>1],-1);
				last[(pos[sa[j]]+1)>>1]=j;
				tr.add(j,1);
			}
			j++;
		}
		ans1[qr[i].id]=tr.query(qr[i].r)-tr.query(qr[i].l-1);
	}
	for(int i=1;i<=m;i++)last[i]=0;
	tr.clear();
	for(int i=1;i<=Q;i++)tr.add(qr[i].l,1);
	for(int i=1,j=1;i<=n;i++)
	{
		while(j<=Q&&qr[j].r<i)tr.add(qr[j].l,-1),j++;
		if(pos[sa[i]]<=2*m)
		{
			ans2[(pos[sa[i]]+1)>>1]+=tr.query(i)-tr.query(last[(pos[sa[i]]+1)>>1]);
			last[(pos[sa[i]]+1)>>1]=i;	
		}
	} 
	for(int i=1;i<=Q;i++)printf("%d\n",ans1[i]);
	for(int i=1;i<=m;i++)printf("%d ",ans2[i]);
	return 0;
}
posted @ 2019-12-23 15:47  nofind  阅读(217)  评论(0编辑  收藏  举报