[AHOI2013]差异
题目大意
给定一个长度为\(n\),由小写字母组成的字符串\(T\),令\(T_i\)表示它从第\(i\)个字符开始的后缀。
求
\[E = \sum_{1\leq i < j \leq n} lcp(T_i , T_j)\]
其中\(lcp(a,b)\)表示串\(a\)与串\(b\)的最长公共前缀。 数据范围:\(n \leq 5*10^5\)
最终答案输出\(\sum_{1\leq i < j \leq n} (len_i + len_j) - 2*E\)。
题解
我们设\(ans_i\)表示\(lcp \ge i\)的\((i,j)\)对数。
那么答案就是\(\sum_{i = 1}^n ans_i\)对吧。(可以类比整数期望公式)
如何求\(ans\)数组?首先求出\(SA\)和\(Height\)数组。
考虑按照\(Height\)从大到小进行合并。
那么合并这两个后缀集合时,两两后缀之间的\(lcp\)大小都会\(\ge Height_{now}\)。
利用这个我们先求出\(lcp\)严格等于\(i\)的答案。 最后求一个后缀和即可。
合并这类的操作可以用并查集实现。 注意\(Height_1=0\)所以不能纳入处理队列中!
实现代码
// luogu-judger-enable-o2
#include<bits/stdc++.h>
#define RG register
#define IL inline
#define _ 501005
#define ll long long
using namespace std;
char c[_] ; int Rank[_],cx[_],cy[_],SA[_],pre[_],Height[_],id[_],fa[_],n,m; ll ans[_],Ans,sz[_];
IL bool cmp(RG int i,RG int j,RG int k){return cy[i]==cy[j] && cy[i + k]==cy[j + k] ; }
IL void GetSA(){
m = 30;
for(RG int i = 1; i <= n; i ++) pre[cx[i] = c[i] - 'a' + 1] ++ ;
for(RG int i = 1; i <= m; i ++) pre[i] += pre[i - 1] ;
for(RG int i = n; i >= 1; i --) SA[pre[cx[i]] --] = i ;
for(RG int p,k = 1; k <= n; k <<= 1){
p = 0 ;
for(RG int i = 1; i <= m; i ++) pre[i] = 0 ;
for(RG int i = n - k + 1; i <= n; i ++) cy[++p] = i ;
for(RG int i = 1; i <= n; i ++) if(SA[i] > k) cy[++p] = SA[i] - k ;
for(RG int i = 1; i <= n; i ++) pre[cx[cy[i]]] ++ ;
for(RG int i = 1; i <= m; i ++) pre[i] += pre[i - 1] ;
for(RG int i = n; i >= 1; i --) SA[pre[cx[cy[i]]] --] = cy[i] ;
for(RG int i = 1; i <= n; i ++) swap(cx[i] , cy[i]) ;
cx[SA[1]] = p = 1;
for(RG int i = 2; i <= n; i ++) cx[SA[i]] = cmp(SA[i] , SA[i - 1] , k) ? p : ++ p ;
if(p >= n) break; m = p ;
}
for(RG int i = 1; i <= n; i ++) Rank[SA[i]] = i ;
for(RG int i = 1,j = 0; i <= n; i ++){
if(j) -- j ;
while(c[i + j] == c[SA[Rank[i] - 1] + j]) ++ j ;
Height[Rank[i]] = j ;
}return ;
}
IL int Find(RG int p){return (fa[p] ^ p) ? fa[p] = Find(fa[p]) : p ; }
IL void Merge(RG int e1,RG int e2,RG int Len){
e1 = Find(e1) ; e2 = Find(e2) ;
ans[Len] += 1ll * sz[e1] * sz[e2] ; sz[e1] += sz[e2] ; fa[e2] = e1 ;
}
IL bool cmp2(RG int i,RG int j){return Height[i] > Height[j] ; }
int main(){
scanf("%s" , c) ; n = strlen(c) ;
for(RG int i = n; i >= 1; i --) c[i] = c[i - 1] ;
GetSA() ;
for(RG int i = 1; i <= n; i ++) fa[i] = id[i] = i , sz[i] = 1;
sort(id + 2 , id + n + 1 , cmp2) ; // id 从 2开始!
for(RG int i = 2; i <= n; i ++) Merge(SA[id[i]] , SA[id[i] - 1] , Height[id[i]]) ;
Ans = 0 ;
for(RG int i = 1; i <= n; i ++) Ans += 1ll * (n - i + 1) * (n - 1) ;
for(RG int i = n; i >= 1; i --) ans[i] = ans[i] + ans[i + 1] ;
for(RG int i = 1; i <= n; i ++) Ans -= 2ll * ans[i] ;
cout << Ans; return 0 ;
}
续:NOI2015 品酒大会
题目自己去看吧:Luogu。
分析一波后发现跟这题是一样。 额外维护一个最大值和最小值即可。