Codeforces 802I - Fake News(hard) 后缀数组+单调栈

Codeforces 802I - Fake News(hard) 后缀数组+单调栈

题目思路

要求求题目不同子串出现次数的平方和。看到本质不同的子串就可以想到SAM+DP或SA+单调栈。这里记录一下SA+单调栈的解决思路。

首先对字符串 s s s建立后缀数组,并求出 h e i g h t height height数组,考虑 h e i g h t height height数组的意义:
h e i g h t [ i ] = L C P ( s a [ i ] , s a [ i − 1 ] ) height[i] = LCP(sa[i], sa[i - 1]) height[i]=LCP(sa[i],sa[i1])
那么显然当 L C P ( s a [ i ] , s a [ i − 1 ] ) < L C P ( s a [ i − 1 ] , s a [ i − 2 ] ) LCP(sa[i], sa[i - 1]) < LCP(sa[i - 1], sa[i - 2]) LCP(sa[i],sa[i1])<LCP(sa[i1],sa[i2])时,某个公共前缀的贡献终止了,此时应当将该公共前缀的贡献统计进答案。这该如何理解?考虑现在 h e i g h t [ i ] ( L C P ) height[i](LCP) height[i](LCP)递增:

在这里插入图片描述

如果秃然遇到了一个更小的 L C P LCP LCP

在这里插入图片描述

那么此时两个阴影覆盖区域的后缀的前缀都终止了贡献。考虑如何统计答案:将答案 n 2 n^2 n2拆分为 2 × ( i − 1 ) + 1 2 \times (i - 1) + 1 2×(i1)+1,然后单独对 2 × ( i − 1 ) 2 \times (i - 1) 2×(i1)的部分进行计数。

维护一个 h e i g h t height height递增的单调栈,每次弹元素的时候统计贡献:贡献等于 2 × n × ( n + 1 ) 2 × l e n 2 \times \frac{n \times(n + 1)}{2} \times len 2×2n×(n+1)×len,统计进答案即可。

Code

#include <bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;

const int N = 1e6 + 10;

namespace SA {
    int sa[N], tax[N], pool1[N], pool2[N], height[N];
    int *rnk = pool1, *tp = pool2;
    void sort(int n, int m) {
        for(int i = 0; i <= m; i++) tax[i] = 0;
        for(int i = 1; i <= n; i++) tax[rnk[i]]++;
        for(int i = 1; i <= m; i++) tax[i] += tax[i - 1];
        for(int i = n; i >= 1; i--) sa[tax[rnk[tp[i]]]--] = tp[i];
    }

    void build(string str, int n, int m) {
        for(int i = 1; i <= n; i++) rnk[i] = str[i] - '0' + 1, tp[i] = i;
        sort(n, m);
        for(int w = 1, p = 0; p < n; w <<= 1, m = p) {
            p = 0;
            for(int i = 1; i <= w; i++) tp[++p] = n - w + i;
            for(int i = 1; i <= n; i++) if(sa[i] > w) tp[++p] = sa[i] - w;
            sort(n, m);
            swap(tp, rnk);
            rnk[sa[1]] = p = 1;
            for(int i = 2; i <= n; i++) rnk[sa[i]] = (tp[sa[i - 1]] == tp[sa[i]] && tp[sa[i - 1] + w] == tp[sa[i] + w]) ? p : ++p;
        }
        int k = 0;
        for(int i = 1; i <= n; i++) {
            if(rnk[i] == 1) continue;
            if(k) --k;
            for(int j = sa[rnk[i] - 1]; str[i + k] == str[j + k]; ++k);
            height[rnk[i]] = k;
        } // Get the height array (height[i] = lcp(sa[i], sa[i - 1]))
    } 

    void reset() {
        memset(sa, 0, sizeof(sa));
        memset(rnk, 0, sizeof(pool1));
        memset(tp, 0, sizeof(pool2));
        memset(tax, 0, sizeof(tax));
        rnk = pool1, tp = pool2;
    }
}

using SA::sa;
using SA::rnk;
using SA::height;

inline void solve() {
    string s; cin >> s;
    int n = s.size();
    SA::build('@' + s, n, 233);
    int ans = n * (n + 1) / 2;
    stack<int> stk;
    for(int i = 2; i <= n + 1; i++) {
        while(stk.size() && height[stk.top()] > height[i]) {
            int y = height[stk.top()]; stk.pop();
            int x = height[i];
            int z = (stk.size() ? height[stk.top()] : 0);
            int len = i - (stk.size() ? stk.top() : 1);
            ans += (y - max(x, z)) * (len * len - len);
        }
        stk.emplace(i);
    }
    cout << ans << endl;
    SA::reset();
}

signed main(){
    ios_base::sync_with_stdio(false), cin.tie(0);
    int t = 1; cin >> t;
    while(t--) solve();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

HeartFireY

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值