BZOJ3473:字符串(后缀数组,主席树,二分,ST表)

时间:2023-03-10 05:58:31
BZOJ3473:字符串(后缀数组,主席树,二分,ST表)

Description

给定n个字符串,询问每个字符串有多少子串(不包括空串)是所有n个字符串中至少k个字符串的子串?

Input

第一行两个整数n,k。
接下来n行每行一个字符串。

Output

一行n个整数,第i个整数表示第i个字符串的答案。

Sample Input

3 1
abc
a
ab

Sample Output

6 1 3

HINT

对于 100% 的数据,1<=n,k<=10^5,所有字符串总长不超过10^5,字符串只包含小写字母。

Solution

加分隔符建出后缀数组,考虑每个后缀的所有前缀,这样就能考虑到所有的子串了。

对于每一个后缀,二分前缀的长度,

然后在二分里面再通过二分左右端点加$ST$表确定$lcp$大于等于之前二分前缀长度的后缀排名区间,

最后用主席树查询一下区间颜色个数是否超过k就行了。查询方法参照HH的项链主席树做法。

话说字符串拼接完了长度是$2\times 10^5$不是$10^5$……数组开小真是太真实了。

Code

 #include<iostream>
#include<cstring>
#include<cstdio>
#define N (200009)
#define LL long long
using namespace std; struct Sgt{int ls,rs,val;}Segt[N*];
int sgt_num,Root[N],a[N],Next[N];
int n,m=,c,k;
int wa[N],wb[N],wt[N];
int Rank[N],SA[N],Height[N];
int ST[N][],LOG2[N];
int ID[N],id_num,End[N];
LL Ans[N];
char r[N],s[N]; bool cmp(int *y,int a,int b,int k)
{
int arank1=y[a];
int brank1=y[b];
int arank2=a+k>=n?-:y[a+k];
int brank2=b+k>=n?-:y[b+k];
return arank1==brank1 && arank2==brank2;
} void Build_SA()
{
int *x=wa,*y=wb;
for (int i=; i<m; ++i) wt[i]=;
for (int i=; i<n; ++i) ++wt[x[i]=r[i]];
for (int i=; i<m; ++i) wt[i]+=wt[i-];
for (int i=n-; i>=; --i) SA[--wt[x[i]]]=i; for (int j=; j<=n; j<<=)
{
int p=;
for (int i=n-j; i<n; ++i) y[p++]=i;
for (int i=; i<n; ++i) if (SA[i]>=j) y[p++]=SA[i]-j; for (int i=; i<m; ++i) wt[i]=;
for (int i=; i<n; ++i) ++wt[x[y[i]]];
for (int i=; i<m; ++i) wt[i]+=wt[i-];
for (int i=n-; i>=; --i) SA[--wt[x[y[i]]]]=y[i]; m=; swap(x,y); x[SA[]]=;
for (int i=; i<n; ++i)
x[SA[i]]=cmp(y,SA[i],SA[i-],j)?m-:m++;
if (m>=n) break;
}
} void Build_Height()
{
for (int i=; i<n; ++i) Rank[SA[i]]=i;
int k=;
for (int i=; i<n; ++i)
{
if (!Rank[i]) continue;
if (k) --k;
int j=SA[Rank[i]-];
while (r[i+k]==r[j+k]) ++k;
Height[Rank[i]]=k;
}
} void Build_ST()
{
for (int i=; i<=n; ++i) LOG2[i]=LOG2[i>>]+;
for (int i=; i<n; ++i) ST[i][]=Height[i];
for (int j=; j<=; ++j)
for (int i=; i+(<<j)-<n; ++i)
ST[i][j]=min(ST[i][j-],ST[i+(<<j-)][j-]);
} int Query(int l,int r)
{
int k=LOG2[r-l+];
return min(ST[l][k],ST[r-(<<k)+][k]);
} int Update(int pre,int l,int r,int x)
{
int now=++sgt_num;
Segt[now]=Segt[pre]; Segt[now].val++;
if (l==r) return now;
int mid=(l+r)>>;
if (x<=mid) Segt[now].ls=Update(Segt[now].ls,l,mid,x);
else Segt[now].rs=Update(Segt[now].rs,mid+,r,x);
return now;
} int Query(int u,int v,int l,int r,int l1,int r1)
{
if (l>r1 || r<l1) return ;
if (l1<=l && r<=r1) return Segt[v].val-Segt[u].val;
int mid=(l+r)>>;
return Query(Segt[u].ls,Segt[v].ls,l,mid,l1,r1)+Query(Segt[u].rs,Segt[v].rs,mid+,r,l1,r1);
} void Build_CT()
{
for (int i=; i<=c; ++i) Next[i]=n;
for (int i=n-; i>=; --i) a[i+]=Next[ID[SA[i]]], Next[ID[SA[i]]]=i;
for (int i=; i<=n; ++i) Root[i]=Update(Root[i-],,n,a[i]);
} bool check(int p,int lim)
{
int l,r,L,R;
l=,r=p-,L=p;
while (l<=r)
{
int mid=(l+r)>>,lcp=Query(mid+,p);
if (lcp>=lim) L=mid, r=mid-;
else l=mid+;
}
l=p+,r=n-,R=p;
while (l<=r)
{
int mid=(l+r)>>,lcp=Query(p+,mid);
if (lcp>=lim) R=mid, l=mid+;
else r=mid-;
}
return Query(Root[L],Root[R+],,n,R+,n)>=k;
} int main()
{
scanf("%d%d",&c,&k);
for (int i=; i<=c; ++i)
{
scanf("%s",s);
for (int j=,l=strlen(s); j<l; ++j)
ID[n]=i, r[n++]=s[j];
End[i]=n; ID[n]=i, r[n++]='#';
}
Build_SA(); Build_Height();
Build_ST(); Build_CT();
for (int i=; i<n; ++i)
{
if (r[SA[i]]=='#') continue;
int l=,r=End[ID[SA[i]]]-SA[i],ans=;
while (l<=r)
{
int mid=(l+r)>>;
if (check(i,mid)) ans=mid, l=mid+;
else r=mid-;
}
Ans[ID[SA[i]]]+=ans;
}
for (int i=; i<=c; ++i)
printf("%lld ",Ans[i]);
}