NOIP2020T2 字符串匹配(递推/DP+筛法枚举+前缀和优化+EXKMP)

题目链接:https://uoj.ac/problem/581

题目大致意思:

数据规模:T<=5, S长度不超过2^20

题解:可以说是非常阴间了放在NOIP第二题。

我们拆解一下问题,首先我们需要枚举若干个(AB)^i,那么每个(AB)^i 从 (AB)^(i-1)  递推出来,中间一定长度的一段子串是否为与对应长度前缀相同,可以哈希去处理,但是精确做法是exkmp算法。

为什么NOIPT2考EXKMP?可能是时代变了吧。

EXKMP算法可以参考百度百科。作用是算出一个串A的所有后缀与串B的公共最大前缀长度。这里不细说了。

EXKMP预处理了S字符串之后,单次比较就是O(1)的复杂度。

i的大小可以依次枚举,根据筛法可以证明n/2 + n/3 + n/4 + ... 约等于 n ln n - n;所以枚举的复杂度也不高。

最后就是枚举满足F(A)<=F(C)的A的个数,复杂度也需要降到O(1)才可能通过此题。

所以我们从前到后递推每一个A前缀对应的数组,对于每一个A的对应前缀。

把其不同的奇数个数字符的前缀字符串个数(没错,就是A前缀对应的所有前缀数量分量),依次存下来,放在numPrefixA数组中,最多0...26中不同的奇数个数的字符。

C也把所有的后缀对应的奇数个数字符个数记录下来,放在oddNumC数组中。

过程中,CountA与CountC用来辅助存每个字符出现的次数。结合代码理解一下过程。

最后,为了枚举simga(1<=F(A)<=F(C)) 所对应的A的个数,把numPrefixA数组以前缀和形式存下来。

    for (uint32_t len = 0; len < S.length() - 1; len++)
        for (uint32_t num = 1; num <= 26; num++) {
            numPrefixA[len][num] += numPrefixA[len][num - 1];
        }

最终,AC的代码如下:

#include "bits/stdc++.h"
using namespace std;
const uint32_t maxN = 1<<20;
int32_t T;
string S;
int32_t AC[maxN + 10];
int32_t exKMP[maxN + 10], numPrefixA[maxN + 10][27], oddNumC[maxN + 10], CountA[255], CountC[255];
void EXKMPS()
{
    exKMP[0] = S.length();
    int32_t pos = 0, maxSuffixPo = 1;
    while (S[pos] == S[pos + 1]) pos++;
    exKMP[1] = pos;
    for (int32_t i = 2; i < S.length(); i++) {
        if (exKMP[i - maxSuffixPo] < exKMP[maxSuffixPo] + maxSuffixPo - i) {
            exKMP[i] = exKMP[i - maxSuffixPo];
        } else {
            int len = exKMP[maxSuffixPo] + maxSuffixPo - i;
            if (len < 0) {
                len = 0;
            }
            maxSuffixPo = i;
            while (S[i + len] == S[len]) len++;
            exKMP[i] = len;
        }
    }
}
void updateOdd(char ch, int32_t* Count, uint8_t &oddC)
{
    Count[ch]++;
    if (Count[ch] & 1) {
        oddC++;
    } else {
        oddC--;
    }
}
void PrepareAC()
{
    uint8_t oddA = 0;
    uint8_t oddC = 0;
    memset(CountA, 0, 255 * sizeof(int32_t));
    memset(CountC, 0, 255 * sizeof(int32_t));
    for (uint32_t len = 0; len < S.length(); len++) {
        updateOdd(S[S.length() - 1 - len], CountC, oddC);
        oddNumC[len] = oddC;
        if (len) {
            for (int num = 0; num <= 26; num++) {
                numPrefixA[len][num] = numPrefixA[len - 1][num];
            }
            updateOdd(S[len - 1], CountA, oddA);
            numPrefixA[len][oddA]++;
        }
    }
    for (uint32_t len = 0; len < S.length() - 1; len++)
        for (uint32_t num = 1; num <= 26; num++) {
            numPrefixA[len][num] += numPrefixA[len][num - 1];
        }
}
int main()
{
    cin >> T;
    while (T--) {
        cin >> S;
        PrepareAC();
        EXKMPS();
        uint64_t ans = 0;
        for (uint32_t i = 2; i < S.length(); i++) {
            for (uint32_t j = i; (j < S.length()) && (exKMP[j - i] >= i); j+=i) {
                ans += numPrefixA[i - 1][oddNumC[S.length() - j - 1]];
            }
        }
        cout << ans << endl;
    }
}

这种究极缝合怪codeforces2200的题目

我不知道它存在NOIPT2的意义

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值