3473: 字符串
Time Limit: 20 Sec Memory Limit: 256 MB
Submit: 109 Solved: 47
[Submit][Status]
Description
给定n个字符串,询问每个字符串有多少子串(不包括空串)是所有n个字符串中至少k个字符串的子串?
Input
第一行两个整数n,k。
接下来n行每行一个字符串。
Output
一行n个整数,第i个整数表示第i个字符串的答案。
Sample Input
3 1
abc
a
ab
abc
a
ab
Sample Output
6 1 3
HINT
对于 100% 的数据,1<=n,k<=10^5,所有字符串总长不超过10^5,字符串只包含小写字母。
Source
题解:
神题一道。。。
继续搬运题解:by云神
首先将所有字符串串在一次做SA,然后我们对于sa上,枚举每个串的每个后缀,求出有几个该后缀的前缀符合条件,那么就要判定区间里面有多少个不同的数,所幸的是这里只需要求是否该数目>=k,所以对于每个位置记录个L(x),表示[L(x),x]中刚好有k个不同的数,且L(x)最大(参考了CF官方题解),然后CF上的题解是对于每个后缀二分出长度,然后是O(n log^2 n的算法),但是O(n log^2 n)在本题仍然会TLE,那么我们发现枚举后缀的时候,如果后缀c+S有n个前缀合法(c表示一个字符,s表示一个串),那么对于后缀S,至少有n-1个前缀合法(如果c+S有n个前缀出现不小于k次,那么其子串也是),那么我们就用类似求SA里的height一样的方法,记录一下前面的后缀的合法前缀数,然后这样的总复杂度就成了均摊O(n log n),可以AC。
一些注释写在代码里
代码:
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<vector>
#include<map>
#include<set>
#include<queue>
#include<string>
#define inf 1000000000
#define maxn 250000+5
#define maxm 500+100
#define eps 1e-10
#define pa pair<int,int>
#define for0(i,n) for(int i=0;i<=(n);i++)
#define for1(i,n) for(int i=1;i<=(n);i++)
#define for2(i,x,y) for(int i=(x);i<=(y);i++)
#define for3(i,x,y) for(int i=(x);i>=(y);i--)
#define mod 1000000007
using namespace std;
inline int read()
{
int x=,f=;char ch=getchar();
while(ch<''||ch>''){if(ch=='-')f=-;ch=getchar();}
while(ch>=''&&ch<=''){x=*x+ch-'';ch=getchar();}
return x*f;
}
int n,m,q,c[maxn],t1[maxn],t2[maxn],sa[maxn],rk[maxn],h[maxn];
int st[maxn][],rec[maxn],cnt[maxn],num[maxn],beg[maxn],end[maxn];
char s[maxn];
void getsa(int m)
{
int *x=t1,*y=t2;
for0(i,m)c[i]=;
for0(i,n)c[x[i]=s[i]]++;
for1(i,m)c[i]+=c[i-];
for3(i,n,)sa[--c[x[i]]]=i;
for(int k=;k<=n+;k<<=)
{
int p=;
for2(i,n-k+,n)y[p++]=i;
for0(i,n)if(sa[i]>=k)y[p++]=sa[i]-k;
for0(i,m)c[i]=;
for0(i,n)c[x[y[i]]]++;
for1(i,m)c[i]+=c[i-];
for3(i,n,)sa[--c[x[y[i]]]]=y[i];
swap(x,y);p=;x[sa[]]=;
for1(i,n)x[sa[i]]=y[sa[i]]==y[sa[i-]]&&y[sa[i]+k]==y[sa[i-]+k]?p:++p;
if(p>=n)break;
m=p;
}
for1(i,n)rk[sa[i]]=i;
for(int i=,k=,j;i<n;h[rk[i++]]=k)
for(k?k--:,j=sa[rk[i]-];s[i+k]==s[j+k];k++);
}
void getst()
{
for1(i,n)st[i][]=h[i];
int k=log2(n);
for1(i,k)for1(j,n-(<<i)+)st[j][i]=min(st[j][i-],st[j+(<<(i-))][i-]);
}
inline int rmq(int x,int y)//求rmq
{
int k=log2(y-x+);
return min(st[x][k],st[y-(<<k)+][k]);
}
inline bool check(int x,int y)//二分出S[x...x+y-1]在整个串中的左右端点
{
int l,r,mid,ll,rr;
if(h[x+]<y)rr=x;
else
{
l=x+;r=n;
while(l<=r)
{
mid=(l+r)>>;
if(rmq(x+,mid)>=y)l=mid+;else r=mid-;
}
rr=r;
}
if(h[x]<y)ll=x;
else
{
l=;r=x-;
while(l<=r)
{
mid=(l+r)>>;
//if(x==37)cout<<l<<' '<<mid<<' '<<r<<' '<<rmq(mid+1,x)<<endl;
if(rmq(mid+,x)>=y)r=mid-;else l=mid+;
}
ll=l;
}
//if(x==37)cout<<y<<' '<<ll<<' '<<rr<<' '<<rec[rr]<<' '<<ll<<endl;
return rec[rr]>=ll;//判断这个范围内是否有k个不同的num值,即出现在不同的k个串中
}
int main()
{
freopen("input.txt","r",stdin);
freopen("output.txt","w",stdout);
m=read();q=read();n=-;
for1(i,m)
{
n++;beg[i]=n;
scanf("%s",s+n);
n=strlen(s);s[n]=' ';end[i]=n-;
}
//printf("%s\n",s);
getsa();
getst();
for1(i,m)for2(j,beg[i],end[i])num[j]=i;//标记所属
int t=,k=;
for1(i,n)if(num[sa[i]])//不能是空字符
{
if(!cnt[num[sa[i]]])k++;
cnt[num[sa[i]]]++;
if(k>=q)
{
for(;k-(cnt[num[sa[t]]]==)>=q;k-=(cnt[num[sa[t]]]==),--cnt[num[sa[t++]]]);
rec[i]=t;
}
}
/*for1(i,n)
{
cout<<i<<' '<<h[i]<<' ';
for2(j,sa[i],n)cout<<s[j];
cout<<endl;
}*/
for1(i,m)
{
long long ans=;int k=;
for2(j,beg[i],end[i])
{
for(k?k--:;k+<=end[i]-j+&&check(rk[j],k+);k++);//类似于height数组的求法
//if(i==1&&s[j]=='b')cout<<"AAAAAAA"<<' '<<k<<' '<<rk[j]<<endl;
ans+=(long long)k;
}
printf("%lld",ans);
if(i!=m)printf(" ");
}
return ;
}