//https://img2018.cnblogs.com/blog/1646268/201908/1646268-20190806114008215-138720377.jpg

NOIP 2020 字符串匹配

首先观察,不难想出一个暴力。

枚举 AB 的右端点,由于 ABABABAB 一定是从第一个字符开始的,C 一定是从右端点到后面结束,那么我们就可以直接再枚举循环节长度以及 AB 中间的断点然后判断即可。

然后发现这个里面有很多地方是不优的,比如我们重复枚举循环节和断点的部分有很多地方重复算了。

我们首先加一个 hash 来判断两个区间内是不是重复的。

这样我们就可以改成枚举循环节长度,然后枚举循环个数来判断,这样我们只要判断开头一个和末尾一个循环节是不是一样就好了,如果不一样,当前循环节长度就可以直接不往后找了,因为前面的都不符合,后面的符合也没用。

然后考虑如何满足 \(F(a)<F(c)\),我们用一个 \(sum1[i]\) 维护 \(1\sim i\) 出现字符数量满足 \(\le j\) 的位置的数量,\(sum2[i]\) 表示 \(i\sim len\) 出现奇数次的字符的个数。

看一下代码:

    for(int i = 2; i < len; i ++)
    {
        ok[a[i - 1] - 'a' + 1] ++;
        if(ok[a[i - 1] - 'a' + 1] & 1) val ++;
        else val --;
        for(int j = val; j <= 26; j ++) sum1[j] ++;
        for(int j = 1; i * j < len; j ++)
        {
            int res = i * j + 1;
            if(check(1, i, i * (j - 1) + 1, i * j)) ans += sum1[sum2[res]];
            else break;
        }
    }

可以看到上面是处理的前 \(i-1\) 个字符的出现奇数次的情况,也就是 \(val\),后面紧接着把 \(sum1\) 更新了,把后面大于等于 \(val\) 的都给加了 \(1\),这表示,如果从 \(i-1\) 断开的话,那么对所有 \(F(C)\ge val\) 的 C 串来说都是可以的,因为当前串 A 串最长是 \(1\sim i-1\) 所以才处理到 \(i-1\) 后面调用 \(sum1\) 来统计答案。


/*
 * @Author: Aisaka_Taiga
 * @Date: 2023-10-28 16:49:24
 * @LastEditTime: 2023-10-28 19:09:43
 * @LastEditors: Aisaka_Taiga
 * @FilePath: \Desktop\P7114.cpp
 * The heart is higher than the sky, and life is thinner than paper.
 */
#include <bits/stdc++.h>

#define ull unsigned long long
#define int long long
#define N 2000100

using namespace std;

inline int read()
{
    int x = 0, f = 1;
    char c = getchar();
    while(c < '0' || c > '9'){if(c == '-') f = -1; c = getchar();}
    while(c <= '9' && c >= '0') x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
    return x * f;
}

const int base = 131;

int T, len, ans, sum1[30], sum2[N], ok[30], val;
ull H[N], P[N];
char a[N];

inline int check(int l1, int r1, int l2, int r2)
{
    ull h1 = (H[r1] - H[l1 - 1] * P[r1 - l1 + 1]);
    ull h2 = (H[r2] - H[l2 - 1] * P[r2 - l2 + 1]);
    if(h1 == h2) return 1;
    else return 0;
}

inline void work()
{
    scanf("%s", a + 1);
    len = strlen(a + 1);
    ans = val = 0;
    for(int i = 1; i <= 26; i ++) ok[i] = 0;
    for(int i = 1; i <= len; i ++)
        H[i] = H[i - 1] * base + a[i];
    for(int i = len; i >= 1; i --)//处理sum2表示 i-n出现奇数次的字符的数量
    {
        ok[(a[i] - 'a') + 1] ++;
        if(ok[a[i] - 'a' + 1] & 1) val ++;
        else val --;
        sum2[i] = val;//当前1-i出现奇数次字符的数量
    }
    val = 0;
    memset(sum1, 0, sizeof sum1);//清空
    for(int i = 1; i <= 26; i ++) ok[i] = 0;
    for(int i = 2; i < len; i ++)//枚举AB的长度
    {
        ok[a[i - 1] - 'a' + 1] ++;
        if(ok[a[i - 1] - 'a' + 1] & 1) val ++;
        else val --;
        for(int j = val; j <= 26; j ++) sum1[j] ++;//后面满足 1-i奇数次的字符数量小于等于j的地方数量
        for(int j = 1; i * j < len; j ++)//枚举是多少倍
        {
            int res = i * j + 1;//C的开头
            // if(check(1, i, i * (j - 1) + 1, i * j))
            //     cout << "SASND: " << sum1[sum2[res]] << endl;
            if(check(1, i, i * (j - 1) + 1, i * j)) ans += sum1[sum2[res]];//统计答案,sum2[res]是C中出现奇数次的字符个数
            else break;//不合法就直接退出
        }
    }
    // for(int i = 1; i <= len; i ++)
    //     cout << sum1[i] << " ";
    // cout << endl;
    cout << ans << endl;
    return ;
}

signed main()
{
    T = read();
    P[0] = 1;
    for(int i = 1; i <= N - 200; i ++) P[i] = P[i - 1] * base;
    while(T --) work();
    return 0;
}
posted @ 2023-10-28 19:24  北烛青澜  阅读(41)  评论(0编辑  收藏  举报