后缀数组+ST表+分块


合法的子串必须满足至少是k个串的字串。这个要求让我们自然而然想到一道相似的题 P5546 [POI2000]公共串 ,这道题用后缀数组很容易想到解法。

于是开始后缀数组乱搞。

先将所有字符串用分隔符隔开连接成一个串。注意,这里的分隔符必须两两不同,否则求出的 \(height\) 数组将会不正确。

在所得串上跑一遍后缀排序,得到 \(height\) 数组。

然后我们使用双指针在 \(height\) 数组上来枚举合法的区间,左右端点的扩展类似于莫队,当区间内出现了 \(K\) 个原串的字串时,此区间合法。

用ST表求出区间内 \(height\) 的最小值 \(t\),即为区间内所有后缀的最长公共前缀,此时区间内所有后缀的长度为 \(t\) 的前缀均为合法字串。

不难发现一个性质:若后缀的长度为 \(t\) 的前缀合法,则该后缀长度为 \(t' \leq t\) 的前缀均合法。也就是说只需要记录每个后缀最长的合法前缀的长度即可。

需要支持区间对一个值取max,并单点查询。

因为 懒得想其他方法 分块好写,所以这里就直接上分块了。


\(\text{Code}:\)

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <cmath>
#define maxn 200005
#define R register
#define INF 0x3f3f3f3f
using namespace std;
typedef long long lxl;

inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9') {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
	return x*f;
}

char s[maxn];
int N,M,K,a[maxn],n;
lxl ans[maxn];
int SA[maxn],rnk[maxn],tax[maxn],tp[maxn],ht[maxn],belong[maxn];

inline void Qsort()
{
	for(int i=0;i<=M;++i) tax[i]=0;
	for(int i=1;i<=N;++i) ++tax[rnk[i]];
	for(int i=1;i<=M;++i) tax[i]+=tax[i-1];
	for(int i=N;i>=1;--i) SA[tax[rnk[tp[i]]]--]=tp[i];
}

inline void SuffixSort()
{
	M=30+n;
	for(int i=1;i<=N;++i)
		rnk[i]=a[i],tp[i]=i;
	Qsort();
	for(int w=1,p=0;p<N;M=p,w<<=1)
	{
		p=0;
		for(int i=1;i<=w;++i) tp[++p]=N-w+i;
		for(int i=1;i<=N;++i) if(SA[i]>w) tp[++p]=SA[i]-w;
		Qsort();
		swap(tp,rnk);
		rnk[SA[1]]=p=1;
		for(int i=2;i<=N;++i)
			rnk[SA[i]]=(tp[SA[i]]==tp[SA[i-1]]&&tp[SA[i]+w]==tp[SA[i-1]+w]) ? p : ++p;
	}
	for(int i=1,k=0;i<=N;++i)
	{
		if(k) --k;
		while(a[i+k]==a[SA[rnk[i]-1]+k]) ++k;
		ht[rnk[i]]=k;
	}
}

struct ST_Table
{
	int d[maxn][30],lg[maxn];
	inline void init()
	{
		lg[0]=-1;
		for(int i=1;i<=N;++i) d[i][0]=ht[i],lg[i]=lg[i>>1]+1;
		for(int j=1;j<=25;++j)
			for(int i=1;i+(1<<(j-1))<=N;++i)
				d[i][j]=min(d[i][j-1],d[i+(1<<(j-1))][j-1]);
	}
	inline int query(int l,int r)
	{
		int k=lg[r-l+1];
		return min(d[l][k],d[r-(1<<k)+1][k]);
	}
}st;

struct BIG_Block// 大分块 (雾)
{
	#define BN 400
	lxl a[maxn],tag[maxn/BN];
	inline int pos(int x) {return (x-1)/BN+1;}
	inline void Get_Max(int l,int r,lxl d)
	{
		int bl=pos(l),br=pos(r);
		for(int i=bl+1;i<br;++i)
			tag[i]=max(tag[i],d);
		if(bl==br)
			for(int i=l;i<=r;++i)
				a[i]=max(a[i],d);
		else
		{
			for(int i=l;i<=bl*BN;++i)
				a[i]=max(a[i],d);
			for(int i=(br-1)*BN+1;i<=r;++i)
				a[i]=max(a[i],d);
		}
	}
	inline void Get_Val(int x)
	{
		a[x]=max(max(a[x],tag[pos(x)]),min(a[x-1],1ll*ht[x]));
	}
}_9baka;

int cnt[maxn],flag;

inline void Add(int i)
{
	if(!belong[i]) return;
	if(!cnt[belong[i]]) ++flag;
	++cnt[belong[i]];
}

inline void Del(int i)
{
	if(!belong[i]) return;
	--cnt[belong[i]];
	if(!cnt[belong[i]]) --flag;
}

int main()
{
	// freopen("CF204E.in","r",stdin);
	n=read(),K=read();
	if(K==1)
	{
		for(int i=1;i<=n;++i)
		{
			scanf(" %s",s+1);
			int len=strlen(s+1);
			printf("%lld ",1ll*(1+len)*len>>1);
		}
		return 0;
	}
	for(int i=1;i<=n;++i)
	{
		scanf(" %s",s+1);
		int len=strlen(s+1);
		for(int j=1;j<=len;++j)
			a[++N]=s[j]-'a'+1,belong[N]=i;
		a[++N]=26+i;
	}
	SuffixSort();
	st.init();
	for(int i=1,j=0;i<=N;++i)
	{
		while(j<=N&&flag<K) Add(SA[++j])
        // 为了使 t 尽量大,这里只枚举出最短的合法区间
		if(flag<K) break;
        // 如果右端点枚举到边界都不合法,则左端点再缩小还是不合法,直接break
		int t=st.query(i+1,j);
		_9baka.Get_Max(i,j,t);
		Del(SA[i]);
	}
	for(int i=2;i<=N;++i)
		_9baka.Get_Val(i);
        //若前后两后缀有公共前缀,则可能前面的后缀更新后面的后缀的答案
	for(int i=1;i<=N;++i)
		ans[belong[SA[i]]]+=_9baka.a[i];
        // 最长合法前缀的长度即为合法前缀的个数
	for(int i=1;i<=n;++i)
		printf("%lld ",ans[i]);
	return 0;
}