bzoj 3238: [AHOI2013]差异
Time Limit: 20 Sec Memory Limit: 512 MB
[Submit][Status][Discuss]
Description
一个长度为N的字符串S,求Σ(任意两个后缀的长度和-它们lcp长度的两倍)
Input
一行,一个字符串S
Output
一行,一个整数,表示所求值
Sample Input
cacao
Sample Output
54
HINT
2<=N<=500000,S由小写英文字母组成
我们知道光是后缀数组是没有什么用的,但是后缀数组可以用来求LCP,原理就是rmq。
求LCP的话需要在原基础上加一个height函数,代表后缀排序之后的数组的相邻位置的LCP。
这样的话求任意两个位置的LCP就变成了求一段区间的rmq。
求height可以做到O(N),需要用到一个玄学的单调性。。。。得按照在原序列中的位置计算。
然后小推一波式子题目就出来了(注意height[1]是0)。
#include<bits/stdc++.h> #define ll long long #define maxn 500005 using namespace std; ll ans=0,tot=0; char s[maxn]; int n,m,st[maxn],tp=0; int sa[maxn],sax[maxn]; int sec[maxn],cc[maxn]; int rank[maxn<<1],rankx[maxn]; int height[maxn],now,k; inline void get_height(){ for(int i=0;i<n;i++) cc[s[i]]++; for(int i=1;i<=500;i++) cc[i]+=cc[i-1]; for(int i=0;i<n;i++) sa[cc[s[i]]--]=i; for(int i=1;i<=n;i++){ rank[sa[i]]=i; if(i>1&&s[sa[i]]==s[sa[i-1]]) rank[sa[i]]=rank[sa[i-1]]; } int len=1; while(len<n){ memset(cc,0,sizeof(cc)); for(int i=0;i<n;i++) cc[sec[i]=rank[i+len]]++; for(int i=n-1;i>=0;i--) cc[i]+=cc[i+1]; for(int i=0;i<n;i++) sax[cc[sec[i]]--]=i; memset(cc,0,sizeof(cc)); for(int i=0;i<n;i++) cc[rank[i]]++; for(int i=1;i<=n;i++) cc[i]+=cc[i-1]; for(int i=1;i<=n;i++) sa[cc[rank[sax[i]]]--]=sax[i]; for(int i=1;i<=n;i++){ rankx[sa[i]]=i; if(i>1&&rank[sa[i]]==rank[sa[i-1]]&&sec[sa[i]]==sec[sa[i-1]]) rankx[sa[i]]=rankx[sa[i-1]]; } for(int i=0;i<n;i++) rank[i]=rankx[i]; len<<=1; } int now=0,j,mx; for(int i=0;i<n;i++){ if(rank[i]==1){ now=0,height[1]=0; continue; } if(now) now--; j=sa[rank[i]-1]; mx=max(j,i); while(mx+now<n&&s[i+now]==s[j+now]) now++; height[rank[i]]=now; } } inline void solve(){ ans=n*(ll)(n+1)*(ll)(n-1)>>1ll; for(int i=1;i<=n;i++){ while(tp&&height[st[tp]]>=height[i]) tp--; st[++tp]=i; for(int j=tp;j;j--) tot+=(ll)(st[j]-st[j-1])*(ll)height[st[j]]; } ans-=tot<<1ll; } int main(){ scanf("%s",s); n=strlen(s); get_height(); solve(); printf("%lld\n",ans); return 0; }
我爱学习,学习使我快乐