2021牛客OI賽前集訓營-提高組(第五場)D-牛牛的border【SAM】
阿新 • • 發佈:2021-10-14
正題
題目連結: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; }