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\n",q[t].ans);
for(int i=1;i<=n;++i)printf("%d ",ans[i]);
puts("");
return 0;
}