[SCOI2012]喵星球上的点名

Link

Description

 一共(n)个人,每个人有两个名字串。再给定(m)个点名串。询问每个点名串是多少人名字串的子串。第二问询问对于每个人,一共有多少点名串是其两名字串(满足一个串即可)的子串。

Solution

SA+莫队。

结论:串(T)是串(S)的子串,应当满足串(S)有某个后缀和串(T)(lcp)(|T|)

我们先把所有串都接在一起,中间用分隔符隔开。然后求一遍SA。

因为SA上每个点向两边扩展,两后缀的lcp就越短,所以我们可以在SA上从每个点名串对应的位置(即串首代表的后缀在SA上的位置)出发,二分出它的合法区间。

第一问转化为:求序列上一段区间的颜色个数。莫队裸题。

对于第二问,考虑差分:当某个颜色数量减少0时,就把他的答案减去剩余询问个数;当当某个颜色数量从0变成1时,就加上剩余询问个数。

tips:

  • 连接两串的分隔符,分隔符应两两不同

     假设有两后缀

    aa#aa!aa...
    aa!aa...
    

     如果分隔符相同:

    aa#aa...
    aa#aa#aa... 
    

     就会导致lcp判长了。

  • 莫队的时候先增后减。就是说移动指针先考虑让数量增加的两种。

Code

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
inline int read(){//be careful for long long!
    register int x=0,f=1;register char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')f=0;ch=getchar();}
    while(isdigit(ch)){x=x*10+(ch^'0');ch=getchar();}
    return f?x:-x;
}

const int N=1e5+10;
int tp,n,m,s[N<<2],col[N<<2],pos[N],len[N],bel[N<<2],cnt[N],ans[N],res,lim=1e4;
struct s_query{int l,r,ans,id;}q[N];
inline bool operator <(const s_query &a,const s_query &b){
    return bel[a.l]==bel[b.l]?(bel[a.l]&1?a.r<b.r:a.r>b.r):bel[a.l]<bel[b.l];
}
inline bool cmp_id(const s_query &a,const s_query &b){return a.id<b.id;}

int sa[N<<2],rnk[N<<2],height[N<<2],tmp[N<<2],A[N<<2],B[N<<2],cntA[N<<2],cntB[N<<2],lg[N<<2],st[N<<2][19];
inline void SA(){
    for(int i=1;i<=tp;++i)++cntA[s[i]];
    for(int i=1;i<=lim;++i)cntA[i]+=cntA[i-1];
    for(int i=tp;i;--i)sa[cntA[s[i]]--]=i;
    rnk[sa[1]]=1;
    for(int i=2;i<=tp;++i){
	rnk[sa[i]]=rnk[sa[i-1]];
	if(s[sa[i]]^s[sa[i-1]])++rnk[sa[i]];
    }
    for(int len=1;rnk[sa[tp]]<tp;len<<=1){
	memset(cntA,0,sizeof(int)*(tp+1));memset(cntB,0,sizeof(int)*(tp+1));
	for(int i=1;i<=tp;++i){
	    ++cntA[A[i]=rnk[i]];
	    ++cntB[B[i]=i+len>tp?0:rnk[i+len]];
	}
	for(int i=1;i<=tp;++i)cntB[i]+=cntB[i-1];
	for(int i=tp;i;--i)tmp[cntB[B[i]]--]=i;
	for(int i=1;i<=tp;++i)cntA[i]+=cntA[i-1];
	for(int i=tp;i;--i)sa[cntA[A[tmp[i]]]--]=tmp[i];
	rnk[sa[1]]=1;
	for(int i=2;i<=tp;++i){
	    rnk[sa[i]]=rnk[sa[i-1]];
	    if(A[sa[i]]^A[sa[i-1]]||B[sa[i]]^B[sa[i-1]])++rnk[sa[i]];
	}
    }
    for(int i=1,j=0;i<=tp;++i){
	if(j)--j;
	while(s[i+j]==s[sa[rnk[i]-1]+j])++j;
	height[rnk[i]]=j;
    }
    for(int i=2;i<=tp;++i)lg[i]=lg[i>>1]+1;
    for(int i=1;i<=tp;++i)st[i][0]=height[i];
    for(int j=1;j<=lg[tp];++j)
	for(int i=1;i+(1<<j)-1<=tp;++i)
	    st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]);
}
inline int Query(int x,int y){
    int k=lg[y-x+1];
    return min(st[x][k],st[y-(1<<k)+1][k]);
}

inline void Add(int pos,int t){
    int c=col[sa[pos]];
    if(!cnt[c]&&c)++res,ans[c]+=m-t+1;
    ++cnt[c];
}
inline void Del(int pos,int t){
    int c=col[sa[pos]];
    --cnt[c];
    if(!cnt[c]&&c)--res,ans[c]-=m-t+1;
}

int main(){
//    freopen("name4.in","r",stdin);freopen("out.out","w",stdout);
    n=read(),m=read();
    for(int t=1;t<=n;++t){
	s[++tp]=++lim;
	for(int i=1,l=read();i<=l;++i)s[++tp]=read(),col[tp]=t;
	s[++tp]=++lim;
	for(int i=1,l=read();i<=l;++i)s[++tp]=read(),col[tp]=t;
    }
    for(int t=1;t<=m;++t){
	s[++tp]=++lim;pos[t]=tp+1;len[t]=read();
	for(int i=1;i<=len[t];++i)s[++tp]=read();
    }
    SA();
    for(int t=1;t<=m;++t){
	int l,r;q[t]=(s_query){rnk[pos[t]],rnk[pos[t]],0,t};
	l=1,r=rnk[pos[t]];
	while(l<=r){
	    int mid=(l+r)>>1;
	    if(Query(mid+1,rnk[pos[t]])>=len[t])q[t].l=mid,r=mid-1;
	    else l=mid+1;
	}
	l=rnk[pos[t]],r=tp;
	while(l<=r){
	    int mid=(l+r)>>1;
	    if(Query(rnk[pos[t]]+1,mid)>=len[t])q[t].r=mid,l=mid+1;
	    else r=mid-1;
	}
    }
    for(int i=1,block=sqrt(tp);i<=tp;++i)bel[i]=(i-1)/block+1;
    sort(q+1,q+m+1);
    for(int t=1,l=1,r=0;t<=m;++t){
	while(l>q[t].l)Add(--l,t);
	while(r<q[t].r)Add(++r,t);
	while(l<q[t].l)Del(l++,t);
	while(r>q[t].r)Del(r--,t);
	q[t].ans=res;
    }
    sort(q+1,q+m+1,cmp_id);
    for(int t=1;t<=m;++t)printf("%d
",q[t].ans);
    for(int i=1;i<=n;++i)printf("%d ",ans[i]);
    puts("");
    return 0;
}
原文地址:https://www.cnblogs.com/fruitea/p/12081523.html