BZOJ3238 [Ahoi2013]差异 【SAM or SA】

BZOJ3238 [Ahoi2013]差异

给定一个串,问其任意两个后缀的最长公共前缀长度的和

1.又是后缀,又是\(lcp\),很显然直接拿\(SA\)\(height\)数组搞就好了,配合一下单调栈

//#pragma GCC optimize("O3")
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<bits/stdc++.h>
using namespace std;
function<void(void)> ____ = [](){ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);};
using LL = int_fast64_t;
const int MAXN = 5e5+7;
struct SA{
    int sa[MAXN],rk[MAXN],c[MAXN],sec[MAXN],height[MAXN],n;
    char s[MAXN];
    void getsa(int m){
        n = strlen(s+1);
        for(int i = 0; i <= m; i++) c[i] = 0;
        for(int i = 1; i <= n; i++) c[rk[i]=s[i]]++;
        for(int i = 1; i <= m; i++) c[i] += c[i-1];
        for(int i = n; i >= 1; i--) sa[c[rk[i]]--] = i;
        for(int k = 1; k <= n; k <<= 1){
            int p = 0;
            for(int i = n - k + 1; i <= n; i++) sec[++p] = i;
            for(int i = 1; i <= n; i++) if(sa[i]>k) sec[++p] = sa[i] - k;
            for(int i = 0; i <= m; i++) c[i] =  0;
            for(int i = 1; i <= n; i++) c[rk[sec[i]]]++;
            for(int i = 1; i <= m; i++) c[i] += c[i-1];
            for(int i = n; i >= 1; i--) sa[c[rk[sec[i]]]--] = sec[i];
            p = 0;
            swap(rk,sec);
            rk[sa[1]] = ++p;
            for(int i = 2; i <= n; i++) rk[sa[i]] = sec[sa[i]]==sec[sa[i-1]] and sec[sa[i]+k]==sec[sa[i-1]+k] ? p : ++p;
            if(p==n) break;
            m = p;
        }
    }
    void getheight(){
        int k = 0;
        for(int i = 1; i <= n; i++){
            if(k) k--;
            int j = sa[rk[i]-1];
            while(s[i+k]==s[j+k]) k++;
            height[rk[i]] = k;
        }
    }
    LL solve(){
        getsa(128);
        getheight();
        LL ret = (n+1ll) * n * (n-1ll) / 2;
        stack<pair<int,int> > stk;
        LL tot = 0;
        for(int i = 1; i <= n; i++){
            int num = 1;
            while(!stk.empty() and height[i]<=height[stk.top().first]){
                num += stk.top().second;
                tot -= 1ll * stk.top().second * (height[stk.top().first] - height[i]);
                stk.pop();
            }
            stk.push(make_pair(i,num));
            tot += height[i];
            ret -= tot * 2;
        }
        return ret;
    }
}sa;
char s[MAXN];
int main(){
    scanf("%s",sa.s+1);
    printf("%lld\n",sa.solve());
    return 0;
}

2.考虑用\(SAM\)来做,把串反转一下,现在要计算的是任意两个前缀的最长公共后缀的和
\(SAM\)有个性质,那就是两个点的\(LCA\)的状态表示的最长串,是这两个点表示的子串的最长公共后缀
所以我们可以枚举所有的\(LCA\),然后计算每一个\(LCA\)的贡献,先计算其任意两棵子树对应的贡献,再计算子树和当前节点产生的贡献

//#pragma GCC optimize("O3")
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<bits/stdc++.h>
using namespace std;
using LL = int_fast64_t;
const int MAXN = 1e6+7;
struct SAM{
    int len[MAXN],link[MAXN],ch[MAXN][26],cnt[MAXN],tot,last;
    vector<int> G[MAXN];
    SAM(){ link[0] = -1; }
    void extend(int c){
        int np = ++tot, p = last;
        len[np] = len[last] + 1; cnt[np] = 1;
        while(p!=-1 and !ch[p][c]){
            ch[p][c] = np;
            p = link[p];
        }
        if(p==-1) link[np] = 0;
        else{
            int q = ch[p][c];
            if(len[p]+1==len[q]) link[np] = q;
            else{
                int clone = ++tot;
                len[clone] = len[p] + 1;
                link[clone] = link[q];
                memcpy(ch[clone],ch[q],sizeof(ch[q]));
                link[np] = link[q] = clone;
                while(p!=-1 and ch[p][c]==q){
                    ch[p][c] = clone;
                    p = link[p];
                }
            }
        }
        last = np;
    }
    void dfs(int u, LL &ret){
        int all = 0;
        for(int i = 0; i < (int)G[u].size(); i++){
            int v = G[u][i];
            dfs(v,ret);
            all += cnt[v];
        }
        for(int i = 0; i < (int)G[u].size(); i++){
            int v = G[u][i];
            ret -= 1ll * (all - cnt[v]) * cnt[v] * len[u];
        }
        ret -= 2ll * all * cnt[u] * len[u];
        cnt[u] += all;
    }
    void solve(char *s){
        int n = strlen(s);
        for(int i = 0; i < n; i++) extend(s[i]-'a');
        for(int i = 1; i <= tot; i++) G[link[i]].push_back(i);
        LL ret = n * (n-1ll) * (n+1ll) / 2;
        dfs(0,ret);
        printf("%lld\n",ret);
    }
}sam;
char s[MAXN];
int main(){
    scanf("%s",s);
    sam.solve(s);
    return 0;
}
posted @ 2020-04-16 15:12  _kiko  阅读(98)  评论(0编辑  收藏  举报