【洛谷P2336】喵星球上的点名

题目

题目链接:https://www.luogu.com.cn/problem/P2336
a180285 幸运地被选做了地球到喵星球的留学生。他发现喵星人在上课前的点名现象非常有趣。
假设课堂上有 \(n\) 个喵星人,每个喵星人的名字由构成。喵星球上的老师会选择 \(m\) 个串来点名,每次读出一个串的时候,如果这个串是一个喵星人的姓或名的子串,那么这个喵星人就必须答到。
然而,由于喵星人的字码如此古怪,以至于不能用 ASCII 码来表示。为了方便描述,a180285 决定用数串来表示喵星人的名字。
现在你能帮助 a180285 统计每次点名的时候有多少喵星人答到,以及 \(m\) 次点名结束后每个喵星人答到多少次吗?

思路

把所有名字和点名全部串在一起,注意任意两个字符串中间要用不同的特殊字符隔开,然后跑一遍 SA,求出 height 并建立 ST 表。
枚举每一个询问串,二分出它在 SA 数组上左右两个端点 \(l,r\) 使得 \(l\sim x\) 的 LCP 和 \(x\sim r\) 的 LCP 均等于该询问串的长度。那么 SA 上在 \([l,r]\) 中所有的喵星人都会对这个询问产生一的贡献。问题转化为多次询问区间内有多少个不同颜色。按询问右端点从小到大排序后,对于每一个颜色记一个 \(last\) 表示最后出现这个颜色是在什么位置,在树状数组上每一个 \(last\) 处加一,然后询问树状数组中 \([l,r]\) 的和即可。
对于第二个询问,我们依然记录 \(last[i]\) 表示颜色 \(i\) 上一次出现的位置,枚举位置,如果这个位置是某个区间开头那么就在树状数组中将这个位置加一,如果是某个区间结尾就将这个区间开头减一。假设这个位置的颜色是 \(i\),那么询问 \((last[i],i]\) 的和即可。
时间复杂度 \(O(n \log n)\)

代码

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

const int N=400010,LG=19;
int n,nn,m,Q,s[N],sa[N],col[N],x[N],y[N],lg[N],c[N],height[N],rk[N],pos[N],len[N],ans1[N],ans2[N],last[N],rmq[N][LG+1];
vector<int> qry[N];

struct Query
{
	int l,r,id;
}ask[N];

bool cmp(Query x,Query y)
{
	return x.r<y.r;
}

void init(int k,int t=-1)
{
	if (t==-1) scanf("%d",&t);
	while (t--)
	{
		scanf("%d",&s[++n]);
		s[n]++; col[n]=k;
	}
	s[++n]=++x[0];
}

void SA()
{
	for (int i=1;i<=n;i++) x[i]=s[i],c[x[i]]++;
	for (int i=2;i<=m;i++) c[i]+=c[i-1];
	for (int i=n;i>=1;i--) sa[c[x[i]]--]=i;
	for (int k=1;k<=n;k<<=1)
	{
		int num=0;
		for (int i=n-k+1;i<=n;i++) y[++num]=i;
		for (int i=1;i<=n;i++) if (sa[i]>k) y[++num]=sa[i]-k;
		for (int i=1;i<=m;i++) c[i]=0;
		for (int i=1;i<=n;i++) c[x[i]]++;
		for (int i=2;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;
		swap(x,y);
		x[sa[1]]=1; num=1;
		for (int i=2;i<=n;i++)
			x[sa[i]]=(y[sa[i]]==y[sa[i-1]] && y[sa[i]+k]==y[sa[i-1]+k]) ? num : ++num;
		m=num;
		if (n==m) break;
	}
}

void geth()
{
	for (int i=1;i<=n;i++) rk[sa[i]]=i;
	for (int i=1,k=0;i<=n;i++)
	{
		if (k) k--;
		int j=sa[rk[i]-1];
		while (s[i+k]==s[j+k]) k++;
		height[rk[i]]=k;
	}
}

void getst()
{
	for (int i=1;i<=n;i++) rmq[i][0]=height[i];
	for (int j=1;j<=LG;j++) 
		for (int i=1;i+(1<<j)-1<=n;i++)
			rmq[i][j]=min(rmq[i][j-1],rmq[i+(1<<(j-1))][j-1]);
}

struct BIT
{
	int c[N];
	
	void add(int x,int v)
	{
		for (int i=x;i<=n;i+=i&-i)
			c[i]+=v;
	}
	
	int query(int x)
	{
		int sum=0;
		for (int i=x;i;i-=i&-i)
			sum+=c[i];
		return sum;
	}
}bit;

int binary1(int x,int t)
{
	int l=1,r=x-1;
	while (l<=r)
	{
		int mid=(l+r)>>1,k=lg[x-mid];
		if (min(rmq[mid+1][k],rmq[x-(1<<k)+1][k])==t) r=mid-1;
			else l=mid+1;
	}
	return r+1;
}

int binary2(int x,int t)
{
	int l=x+1,r=n;
	while (l<=r)
	{
		int mid=(l+r)>>1,k=lg[mid-x];
		if (min(rmq[x+1][k],rmq[mid-(1<<k)+1][k])==t) l=mid+1;
			else r=mid-1;
	}
	return l-1;
}

int main()
{
	scanf("%d%d",&nn,&Q);
	x[0]=2e5; lg[1]=0;
	for (int i=2;i<N;i++)
		lg[i]=lg[i>>1]+1;
	for (int i=1;i<=nn;i++) init(i),init(i);
	for (int i=1;i<=Q;i++)
	{
		pos[i]=n+1; scanf("%d",&len[i]);
		init(0,len[i]);
	}
	x[0]=0; m=4e5;
	SA(); geth(); getst();
	for (int i=1;i<=Q;i++)
	{
		ask[i].l=binary1(rk[pos[i]],len[i]);
		ask[i].r=binary2(rk[pos[i]],len[i]);
		ask[i].id=i;
	}
	sort(ask+1,ask+1+Q,cmp);
	for (int i=1,j=1;i<=Q;i++)
	{
		for (;j<=ask[i].r;j++)
		{
			if (!col[sa[j]]) continue;
			if (last[col[sa[j]]]) bit.add(last[col[sa[j]]],-1);
			bit.add(j,1);
			last[col[sa[j]]]=j;
		}
		ans1[ask[i].id]=bit.query(ask[i].r)-bit.query(ask[i].l-1);
	}
	memset(bit.c,0,sizeof(bit.c));
	memset(last,0,sizeof(last));
	for (int i=1;i<=Q;i++)
	{
		qry[ask[i].l].push_back(i);
		qry[ask[i].r+1].push_back(i);
	}
	for (int i=1;i<=n;i++)
	{
		for (int j=0;j<qry[i].size();j++)
		{
			if (ask[qry[i][j]].l==i) bit.add(i,1);
			if (ask[qry[i][j]].r==i-1) bit.add(ask[qry[i][j]].l,-1);
		}
		if (col[sa[i]])
		{
			ans2[col[sa[i]]]+=bit.query(i)-bit.query(last[col[sa[i]]]);
			last[col[sa[i]]]=i;
		}
	}
	for (int i=1;i<=Q;i++)
		printf("%d\n",ans1[i]);
	for (int i=1;i<=nn;i++)
		printf("%d ",ans2[i]);
	return 0;
}
posted @ 2020-11-27 06:53  stoorz  阅读(78)  评论(0编辑  收藏  举报