正题
题目链接:https://www.luogu.com.cn/problem/P2336
题目大意
n n n个名字(每个名字两个串), m m m次点名,如果一次点名里是一个名字两个串中的子串该人就要答到。
对于每次点名求多少个人答到,每个名字求答到多少次。
解题思路
先考虑第一问,我们将所有串串在一起(中间加大数)然后跑出 S A SA SA和所有的 L C P LCP LCP。
然后对于每个点名串起始位置 k k k我们寻找一个区间 [ l , r ] [l,r] [l,r]使得 L C P ( k , i ) ≥ l e n ( i ∈ [ l , r ] ) LCP(k,i)\geq len(i\in[l,r]) LCP(k,i)≥len(i∈[l,r])
这个区间我们求出 h e i g h t height height后用 S T ST ST表+二分可以求出
这样如果任意一个名字串的在这个区间内就会被点到。因为有名有姓,我们把同一个名字的 S A SA SA染成同一种颜色,这样问题就变为了在 [ l , r ] [l,r] [l,r]这个区间内有多少种颜色
而第二问也变为了有多少个区间包含该种颜色,我们直接用树状数组做就好了。
时间复杂度 O ( n log n ) O(n\log n) O(nlogn)
c o d e code code
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#define lowbit(x) (x&-x)
using namespace std;
const int N=5e5+10;
struct node{
int l,r,id;
}q[N];
int n,m,num,Q,s[N];
int col[N],len[N],head[N];
int sa[N],c[N],x[N],y[N];
int rk[N],height[N];
int st[N][20],lg[N];
int pre[N],last[N],qq[N];
int ans1[N],ans2[N];
struct Tree_Array{
int t[N];
void Change(int x,int val){
if(!x) return;
while(x<=n){
t[x]+=val;
x+=lowbit(x);
}
return;
}
int Ask(int x){
int ans=0;
while(x){
ans+=t[x];
x-=lowbit(x);
}
return ans;
}
}T1,T2;
void Init(){
scanf("%d%d",&num,&Q);
m=1e4;
for(int i=1;i<=num;i++){
for(int j=0;j<2;j++){
int x,c;
scanf("%d",&x);
while(x--){
scanf("%d",&c);
s[++n]=c;col[n]=i;
}
s[++n]=++m;
}
}
for(int i=1;i<=Q;i++){
int x;head[n+1]=i;
scanf("%d",&len[i]);
for(int j=1;j<=len[i];j++){
scanf("%d",&x);
s[++n]=x;col[n]=-i;
}
s[++n]=++m;
}
}
void Qsort(){
for(int i=1;i<=m;i++)c[i]=0;
for(int i=1;i<=n;i++)c[x[i]]++;
for(int i=1;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;
return;
}
void Get_SA(){
for(int i=1;i<=n;i++)
x[i]=s[i],y[i]=i;
Qsort();
for(int w=1;w<=n;w<<=1){
int p=0;
for(int i=n-w+1;i<=n;i++) y[++p]=i;
for(int i=1;i<=n;i++)
if(sa[i]>w) y[++p]=sa[i]-w;
Qsort();swap(x,y);x[sa[1]]=p=1;
for(int i=2;i<=n;i++)
x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+w]==y[sa[i-1]+w])?p:++p;
if(p==n) break;m=p;
}
return;
}
void Get_Height(){
int k=0;
for(int i=1;i<=n;i++)rk[sa[i]]=i;
for(int i=1;i<=n;i++){
//if(rk[i]==1) continue;
if(k) k--;int j=sa[rk[i]-1];
while(i+k<=n&&j+k<=n&&s[i+k]==s[j+k]) k++;
height[rk[i]]=k;
}
return;
}
void Get_ST(){
lg[0]=-1;
for(int i=1;i<=n;i++)
st[i][0]=height[i],lg[i]=lg[i>>1]+1;
for(int j=1;j<19;j++)
for(int i=1;i+(1<<j-1)<=n;i++)
st[i][j]=min(st[i][j-1],st[i+(1<<j-1)][j-1]);
return;
}
int LCP(int l,int r){
if(l==r) return 1e9+7;
if(l>r) swap(l,r);l++;
int z=lg[r-l+1];
return min(st[l][z],st[r+1-(1<<z)][z]);
}
bool cmp(node x,node y)
{return x.r<y.r;}
void Get_Pre(){
for(int i=1;i<=n;i++){
if(col[sa[i]]>0){
pre[i]=last[col[sa[i]]];
last[col[sa[i]]]=i;
}
if(head[i]){
int l=1,r=rk[i];
while(l<=r){
int mid=(l+r)>>1;
if(LCP(mid,rk[i])>=len[head[i]]) r=mid-1;
else l=mid+1;
}
q[head[i]].l=l;
l=rk[i];r=n;
while(l<=r){
int mid=(l+r)>>1;
if(LCP(rk[i],mid)>=len[head[i]]) l=mid+1;
else r=mid-1;
}
q[head[i]].r=r;q[head[i]].id=head[i];
qq[q[head[i]].l]++;
}
}
sort(q+1,q+1+Q,cmp);
}
void Get_Ans(){
int h=1,z=1;
for(int i=1;i<=n;i++){
T2.Change(i,qq[i]);
if(col[sa[i]]>0){
T1.Change(pre[i],-1);T1.Change(i,1);
ans2[col[sa[i]]]+=T2.Ask(i)-T2.Ask(pre[i]);
}
while(h<=Q&&q[h].r<=i){
ans1[q[h].id]+=T1.Ask(q[h].r)-T1.Ask(q[h].l-1);
T2.Change(q[h].l,-1);h++;
}
}
return;
}
int main()
{
Init();
Get_SA();
Get_Height();
Get_ST();
Get_Pre();
Get_Ans();
for(int i=1;i<=Q;i++)
printf("%d\n",ans1[i]);
for(int i=1;i<=num;i++)
printf("%d ",ans2[i]);
}