[AHOI2013]差异

[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
分析一波后发现跟这题是一样。 额外维护一个最大值和最小值即可。

猜你喜欢

转载自www.cnblogs.com/GuessYCB/p/8939627.html
今日推荐