【Manacher】 【求解本质不同子串】【“蔚来杯“2022牛客暑期多校训练营9】【 G. Magic Spells】【字符串HASH】

Manacher 求解本质不同子串

1. Manacher 可以在O(n)的时间维护一个pnum数组(定义参考3)
2. 对于一个字符串
a b b a                 length = 4
首先使用char s[maxn]保存
const int maxn = 3e5+5;
char s[maxn];
scanf("%s", s);
3. 将其构造为新串
$ # a # b # b # a #     length = 4*2 + 2
          i       r     pnum[i] = r - i + 1
此时 # * # * # 有效部分长度一定为奇数
char str[maxn << 1];
void Manacher(int &n){
    str[0] = '$';
    str[1] = '#';
    for(int i=0; i<n; i++)
        str[(i<<1)+2] = s[i], str[(i<<1)+3] = '#';
    n = n*2 + 2;
    str[n] = 0;
}
4. 既然是O(n)的算法,求解pnum[i]时,pnum[i-1]等已知
可以记录下右端点r最靠右,即pnum[i]+i最大时的i和p[i]+i
$ # a # b # b # a #
    i   r               id = i, mx = r = i + pnum[i]
int mx = 0, id;
if(p[i]+i>mx)
    mx = p[i]+i, id = i;
5. 下一次匹配时可以参考mx以及id
$  #  a  #  b  #  b  #  a  #
mx'         i' id i          mx

当i < mx时,i' = id - (i - id) = 2*id - i
pnum[i']已知,由回文串的性质,pnum[i] = pnum[i']

当然,pnum[i]不能大于mx - i,因为超出该范围后不能利用回文串的性质

void Manacher(int &n){
    str[0] = '$';
    str[1] = '#';
    for (int i = 0; i < n; i++)
        str[(i << 1) + 2] = s[i], str[(i << 1) + 3] = '#';
    n = 2 * n + 2;
    str[n] = 0;
    int mx = 0, id;
    for (int i = 1; i < n; i++){
        pnum[i] = mx > i ? min(pnum[2 * id - i], mx - i) : 1;
        // while循环部分只是普通的暴力过程
        while (str[i - pnum[i]] == str[i + pnum[i]])
            pnum[i]++;
        if (pnum[i] + i > mx)
            mx = pnum[i] + i, id = i;
    }
}
6. 可以发现,所有
p[i] = mx > i ? min(p[2 * id - i], mx - i) : 1;
这一部分的回文串都是利用了之前相同的回文串,排除这一部分似乎就可以得到本质不同的回文子串了

因此我们在while循环部分添加如下代码
set<pair<ull, ull>> strSet;
while(str[i-pnum[i]] == str[i+pnum[i]]){
    pnum[i]++;
    // ============================寻找本质不同子串
    // 排除 #
    if(pnum[i]<2)
        continue;
    int l = i - pnum[i] + 1;
    int r = i + pnum[i] - 1;
    // 排除 a # a 
    // 保留 # a # a #
    if(str[l]!='#')
        continue;
    strSet.insert({l, r});
    // ============================
}
7. 然而...
$  #  a  #  b  #  b  #  a  #
   1  1  1           2  2  2
此时相同的1串和2串都会被加入set中
此时需要利用字符串HASH去除本质重复的情况
这里就不写了,下面的题会用到

练习——牛客"蔚来杯"9-G

过这道题的完整代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 3e5 + 10;
typedef unsigned long long ull;

// Manacher ini data
char s[maxn], str[maxn << 1];
int pnum[maxn << 1];
set<pair<int, int>> strSet;

// str Hash ini data
const ull S1 = 146527;
const ull M1 = 1000000009;
const ull S2 = 19260817;
const ull M2 = 998244353;

// multi check
map<pair<ull, ull>, int> strMap;
int T;
int F = 0;

template<ull SEED, ull MOD>
struct Hash{
    vector<ull> p, h;
    Hash(){}
    Hash(const char s[], int n){
        p.resize(n + 1), h.resize(n + 1);
        p[0] = 1;
        for (int i = 1; i <= n; i++)
            p[i] = p[i - 1] * SEED % MOD;
        for (int i = 1; i <= n; i++)
            h[i] = (h[i - 1] * SEED % MOD + s[i - 1]) % MOD;
    }
    ull get(int l, int r){
        return (h[r] - h[l] * p[r - l] % MOD + MOD) % MOD;
    }
};

void clearManacher(){
    memset(s, 0, sizeof s);
    memset(str, 0, sizeof str);
    memset(pnum, 0, sizeof pnum);
    strSet.clear();
}

void Manacher(int &n){
    F++;
    str[0] = '$';
    str[1] = '#';
    for (int i = 0; i < n; i++)
        str[(i << 1) + 2] = s[i], str[(i << 1) + 3] = '#';
    n = 2 * n + 2;
    int mx = 0, id;
    for (int i = 1; i < n; i++){
        pnum[i] = mx > i ? min(pnum[2 * id - i], mx - i) : 1;
        while(str[i-pnum[i]] == str[i+pnum[i]]){
            pnum[i]++;
            // ============================寻找本质不同子串
            if(pnum[i]<2)
                continue;
            int l = i - pnum[i] + 1;
            int r = i + pnum[i] - 1;
            if(str[l]!='#')
                continue;
            strSet.insert({l, r});
            // ============================
        }
        if(pnum[i]+i>mx)
            mx = pnum[i] + i, id = i;
    }
}

int main(){
    scanf("%d", &T);
    int memt = T;
    while(T--){
        clearManacher();
        scanf("%s", s);
        int slen = strlen(s);
        Manacher(slen);
        Hash<S2, M2> strHash2(str, slen);
        Hash<S1, M1> strHash1(str, slen);
        for(auto i:strSet){
            ull strElem1 = strHash1.get(i.first, i.second + 1);
            ull strElem2 = strHash2.get(i.first, i.second + 1);
            pair<ull, ull> hashValue = {strElem1, strElem2};
            // =========================================================
            if (F > 1 && strMap.find(hashValue) == strMap.end())
                continue;
            if (F > 1 && strMap[hashValue] < F - 1){
                strMap.erase(hashValue);
                continue;
            }
            if (strMap[hashValue] == F)
                continue;
            // =========================================================
            strMap[{strElem1, strElem2}] = F;
        }
    }
    int ans = 0;
    for(auto i:strMap){
        if(i.second == F)
            ans++;
    }
    printf("%d\n", ans);
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值