1. 程式人生 > 其它 >2021牛客OI賽前集訓營-提高組(第五場)D-牛牛的border【SAM】

2021牛客OI賽前集訓營-提高組(第五場)D-牛牛的border【SAM】

正題

題目連結:https://ac.nowcoder.com/acm/contest/20110/D


題目大意

求一個長度為\(n\)的字串的所有子串的\(border\)長度和。

\(1\leq n\leq 10^5\)


解題思路

考慮到兩個相同的子串會作為一個子串的\(border\),所以問題可以變為求所有相同子串對的長度之和。

然後直接跑出\(SAM\)然後對於每個節點統計它在字串裡的出現次數,然後所有的\(len_{fa+1}\sim len_x\)都是這個節點的字串長度,用過等比序列求和就好了。

時間複雜度:\(O(n)\)(不算快速排序)


code

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const ll N=2e5+10;
ll n,cnt,last,ans,p[N],len[N],fa[N],c[N],ch[N][26];
char s[N];
void Insert(char c){
	ll p=last,np=last=++cnt;
	len[np]=len[p]+1;
	for(;!ch[p][c];p=fa[p])ch[p][c]=np;
	if(!p)fa[np]=1;
	else{
		ll q=ch[p][c];
		if(len[p]+1==len[q])fa[np]=q;
		else{
			ll nq=++cnt;len[nq]=len[p]+1;
			memcpy(ch[nq],ch[q],sizeof(ch[nq]));
			fa[nq]=fa[q];fa[q]=fa[np]=nq;
			for(;ch[p][c]==q;p=fa[p])ch[p][c]=nq;
		}
	}
	return;
}
bool cmp(ll x,ll y)
{return len[x]>len[y];}
ll calc(ll l,ll r){
	if(!r)return 0;
	return (r+l)*(r-l+1)/2;
}
signed main()
{
	scanf("%lld",&n);
	scanf("%s",s+1);cnt=last=1;
	for(ll i=1;i<=n;i++)Insert(s[i]-'a'),c[last]++;
	for(ll i=1;i<=cnt;i++)p[i]=i;
	sort(p+1,p+1+cnt,cmp);
	for(ll i=1;i<=cnt;i++)c[fa[p[i]]]+=c[p[i]];
	for(ll i=1;i<=cnt;i++){
		ll w=calc(len[fa[i]]+1,len[i]);
		ans+=calc(1,c[i]-1)*w;
	}
	printf("%lld\n",ans);
	return 0;
}