题目链接: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;
}