2021ICPC EC final B. Beautiful String题解

今天跟队友vp21年EC final,最后可惜计算几何没开出来,以及J题时间不够没做出来,主要就是B做太麻烦了,导致花了太多时间。但是作为串串人,还是非常喜欢字符串题,这里写一下我们的B题做法。

题意

定义一个好串是能将字符串分为6个部分 s1+s2+s3+s4+s5+s6 并且满足s1=s2=s5,s3=s6。给定一个字符串,求对于所有子串,是好串的个数。字符串长度是 5×103级别,可以 O(n2)处理。

分析

最核心的想法如下图所示:
image
利用S2S3等于S5S6,将两者捆绑起来,就可以用一个类似LCP的东西来求,我们是用的KMP。
具体来说,首先枚举S1的起始位置和长度,就已经 O(n2) 了枚举完S1就可以哈希判一下右边那段S2是不是跟S1相等,然后再去枚举 S2+S3,答案相当于 S2+S3 在右边的出现次数(中间要至少有一个空作为 S4),然后这个统计出现次数就可以利用kmp来作。
先忽略掉 S1,我们相当于要处理的是对于每个区间 [l,r], 我们要求从 r+2位置开始,出现了多少次等于这个区间 [l,r] 的次数。
那么我们去枚举 l, 截取字符串 [l,n], 相当于对于每个前缀,求不重叠的出现次数。反过来等于对于每个前缀,去跳border,从长度小于前缀的一半开始计数,看能跳多少次,然后在border树上差分一下。这个过程就是NOI2014动物园,一开始我想建出border树后用倍增跳,但是T了,最后还是用动物园那题的 O(n)做法才能做。具体的就是做2次kmp,第二次去维护 Nxt[i] 数组,表示从 i 开始跳到的第一个小于一半长度的位置,然后维护过程跟普通的kmp类似,可以往后接一个字母就接,如果长度大等于一半了就跳一次nxt[]
我的代码里将这部分预处理在了 dp[i][j] 数组,然后发现在计算答案的时候还需要一个后缀和,存在 sufsum[i][j],然后就可以枚举S1的位置长度,利用后缀和数组 O(1) 统计答案。

代码

#include <bits/stdc++.h>
#define int long long
#define ll __int128_t
#define pii pair<int, int>
using namespace std;
const int maxn = 5e3 + 10;
const int mod = 1e18 + 201;
const int base = 131;
int nxt[maxn], Nxt[maxn];
vector<int> G[maxn];
int dp[maxn][maxn], dep[maxn], sub[maxn];
int sufsum[maxn][maxn];
void get_nxt(string& str) {
    for (int i = 1, j = 0; i < str.length(); i++) {
        while (j && str[i] != str[j])
            j = nxt[j - 1];
        if (str[i] == str[j])
            j++;
        nxt[i] = j;
    }
}
void get_nxt2(string& str) {
    for (int i = 1, j = 0; i < str.length(); i++) {
        while (j && str[i] != str[j])
            j = nxt[j - 1];
        if (str[i] == str[j])
            j++;
        if (j * 2 >= i + 1)
            j = nxt[j - 1];
        Nxt[i] = j;
    }
}
void dfs2(int u) {
    for (auto v : G[u])
        dfs2(v), sub[u] += sub[v];
}
struct HASH {
    int hash[maxn];
    int mi[maxn];
    void init(string& str) {
        int n = str.length();
        mi[0] = 1;
        for (int i = 1; i <= n; i++)
            mi[i] = (ll)mi[i - 1] * base % mod;
        for (int i = 1; i <= n; i++)
            hash[i] = ((ll)hash[i - 1] * base + str[i - 1]) % mod;
    }
    int get_hash(int l, int r) {
        return ((ll)hash[r] - (ll)hash[l - 1] * mi[r - l + 1] % mod + mod) %
               mod;
    }
} Hash;
void solve() {
    string str;
    cin >> str;
    int n = str.length();
    for (int i = 1; i <= n; i++) {
        int len = n - i + 1;
        for (int j = 0; j <= len; j++)
            G[j].clear(), sub[j] = 0;
        string tem = str.substr(i - 1, len);
        get_nxt(tem);
        get_nxt2(tem);
        for (int j = 1; j <= len; j++)
            sub[Nxt[j - 1]]++;
        for (int j = 1; j <= len; j++)
            G[nxt[j - 1]].push_back(j);
        dfs2(0);
        for (int j = 1; j <= len; j++)
            dp[i][i + j - 1] = sub[j], sufsum[i][i + j - 1] = sub[j];
        for (int j = len - 1; j >= 1; j--)
            sufsum[i][i + j - 1] += sufsum[i][i + j];
    }
    Hash.init(str);
    int ans = 0;
    int debug = 0;
    for (int i = 1; i <= n; i++) {
        for (int s1len = 1; i + 2 * s1len - 1 <= n; s1len++) {
            // s1 [i, i + s1len - 1],   s2[i + s1len, i + 2 * s1len - 1]
            int l1 = i, r1 = i + s1len - 1;
            int l2 = i + s1len, r2 = i + 2 * s1len - 1;
            if (Hash.get_hash(l1, r1) != Hash.get_hash(l2, r2))
                continue;
            if (r2 + 1 > n)
                continue;
            ans += sufsum[l2][r2 + 1];
        }
    }
    cout << ans << "\n";
}

signed main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while (t--)
        solve();
    return 0;
}
posted @   TJUHuangTao  阅读(17)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
点击右上角即可分享
微信分享提示