P2336 [SCOI2012] 喵星球上的点名 题解

P2336 [SCOI2012] 喵星球上的点名

考虑后缀数组的常见套路:把所有串中间用奇怪字符拼在一起,记录每个位置上的字符是哪个文本串的,求出 \(\rm sa\)\(\rm height\)

看到子串,显然转化为后缀数组上的 LCP 问题。又由那条经典性质:\(\operatorname{LCP}(\text{sa}(i),\text{sa}(j))=\min_{i<k\le j}\{\text{height}(k)\}\),可以得到一个重要结论:找到一个后缀在 height 数组上的位置后,向两边走 RMQ 一定不会变得更大,也就是说满足条件的后缀位于一个区间内。

于是我们考虑对于每个模式串,二分找到其向左和向右最远能够到达哪个后缀,使得它和这个后缀的 LCP 长度不小于这个模式串的长度。于是询问被我们转化成了一个个区间,每个询问实际上包含两个问题:

  1. 每个区间有多少种数;
  2. 每种数被多少区间包括。

又是 \(50000\) 的数据范围又允许离线,那显然莫队了。第一问就是莫队的板题,不再赘述;第二问的处理方法比较特殊,因为在计算它的过程中我们必须保证单次 \(O(1)\)。这里我们采用差分的思想,当这种数第一次出现时,给它的答案加上最大可能出现次数,也就是剩余数的数量;在它消失时,再减去剩余数的数量,就统计出了答案。

代码实现方面,难点主要在于二分的准确性。因为每个模式串有可能连一个文本串也匹配不上,但它又不能匹配上自己,这就要求给不合法区间的 \(l,r\) 设置为 \(l>r\) 来体现其不合法,就不会算入多余答案。

constexpr int MAXN=4e5+50;
int N,M,n,s[MAXN],bg[MAXN],len[MAXN];
int sa[MAXN],rk[MAXN],rk2[MAXN],id[MAXN],cnt[MAXN];
int h[MAXN];
int col[MAXN],f[MAXN][21];
int t,sm,ans[MAXN],res[MAXN];
struct Ask{
	int l,r,id;
	bool operator<(const Ask&x)const{
		return l/t!=x.l/t?l<x.l:l/t&1?r<x.r:r>x.r;
	}
}q[MAXN];

void getsa(int m){
	for(int i=1;i<=n;i++) cnt[rk[i]=s[i]]++;
	for(int i=1;i<=m;i++) cnt[i]+=cnt[i-1];
	for(int i=n;i;i--) sa[cnt[rk[i]]--]=i;
	for(int w=1,p,cur;;w<<=1,m=p){
		cur=0;
		for(int i=n-w+1;i<=n;i++) id[++cur]=i;
		for(int i=1;i<=n;i++) if(sa[i]>w) id[++cur]=sa[i]-w;
		memset(cnt,0,(m+1)<<2);
		for(int i=1;i<=n;i++) cnt[rk[i]]++;
		for(int i=1;i<=m;i++) cnt[i]+=cnt[i-1];
		for(int i=n;i;i--) sa[cnt[rk[id[i]]]--]=id[i];
		p=0;
		memcpy(rk2,rk,(n+1)<<2);
		for(int i=1;i<=n;i++)
			rk[sa[i]]=rk2[sa[i]]==rk2[sa[i-1]]&&rk2[sa[i]+w]==rk2[sa[i-1]+w]?p:++p;
		if(p==n) break;
	}
}
void geth(){
	for(int i=1,k=0;i<=n;i++){
		if(!rk[i]) continue;
		if(k) k--;
		while(s[i+k]==s[sa[rk[i]-1]+k]) k++;
		h[rk[i]]=k;
	}
}
void ST(){
	for(int i=1;i<=n;i++) f[i][0]=h[i];
	for(int j=1;j<=20;j++)
		for(int i=1;i+(1<<j)-1<=n;i++)
			f[i][j]=min(f[i][j-1],f[i+(1<<(j-1))][j-1]);
}
int amn(int l,int r){
	int s=__lg(r-l+1);
	return min(f[l][s],f[r-(1<<s)+1][s]);
}
int bs1(int x,int val){
	int l=1,r=x,ans=x;
	while(l<=r){
		int mid=(l+r)>>1;
		if(amn(mid,x)>=val) ans=mid-1,r=mid-1;
		else l=mid+1;
	}
	return ans;
}
int bs2(int x,int val){
	int l=x+1,r=n,ans=x-1;
	while(l<=r){
		int mid=(l+r)>>1;
		if(amn(x+1,mid)>=val) ans=mid,l=mid+1;
		else r=mid-1;
	}
	return ans;
}
void add(int x,int id){
	if(++cnt[col[sa[x]]]==1&&col[sa[x]]){
		sm++;
		res[col[sa[x]]]+=M-id+1;
	}
}
void del(int x,int id){
	if(cnt[col[sa[x]]]--==1&&col[sa[x]]){
		sm--;
		res[col[sa[x]]]-=M-id+1;
	}
}

int main(){
	N=read(),M=read();
	for(int i=1,l;i<=N;i++){
		l=read();
		for(int j=1;j<=l;j++){
			s[++n]=read();
			col[n]=i;
		}
		s[++n]=2e4+1;
		l=read();
		for(int j=1;j<=l;j++){
			s[++n]=read();
			col[n]=i;
		}
		s[++n]=2e4+1;
	}
	for(int i=1;i<=M;i++){
		len[i]=read();
		bg[i]=n+1;
		for(int j=1;j<=len[i];j++) s[++n]=read();
		if(i<M) s[++n]=2e4+1;
	}
	getsa(2e4+1),geth(),ST();
	t=sqrt(n);
	for(int i=1;i<=M;i++) q[i]={bs1(rk[bg[i]],len[i]),bs2(rk[bg[i]],len[i]),i};
	sort(q+1,q+M+1);
	memset(cnt,0,(N+1)<<2);
	for(int i=1,l=1,r=0;i<=M;i++){
		while(l>q[i].l) add(--l,i);
		while(r<q[i].r) add(++r,i);
		while(l<q[i].l) del(l++,i);
		while(r>q[i].r) del(r--,i);
		ans[q[i].id]=sm;
	}
	for(int i=1;i<=M;i++) write(ans[i]);
	for(int i=1;i<=N;i++) write(res[i],' ');
	return putchar('\n'),fw,0;
}
posted @ 2025-02-23 20:22  Laoshan_PLUS  阅读(15)  评论(0)    收藏  举报