C++ manacher(求解回文串问题)

框架代码,对应蓝桥云课 最长回文子串 代码见下

cpp 复制代码
#include <iostream>
#include <cstring>
using namespace std;

#define maxn 1000010
#define split '$'
int p[maxn];
char strTmp[maxn];

void ManacherPre(char* str) {
    strcpy(strTmp, str);
    int i = 0;
    for (; strTmp[i]; ++i) {
        str[2 * i] = split;
        str[2 * i + 1] = strTmp[i];
    }
    str[2 * i] = split;
    str[2 * i + 1] = '\n';
}

bool ManacherMatch(char a, char b) {
    return a == b;
}

int Manacher(char * str) {
    ManacherPre(str);
    // ct : 当前已知的最优回文区域的中心位置
    int ct = 0;
    // r : 当前已知的最右回文区域的右边界(ct + p[ct])
    int r = 0;
    int maxLen = 1;
    // p[] : 记录每个位置的回文半径
    p[0] = 1;
    for (int i = 1; str[i]; ++i) {
        // 1 计算p[i]的初始值
        if (i < r) {
            p[i] = min(p[2 * ct - i], r - i);
        }
        else {
            p[i] = 0;
        }
        // 2 扩张p[i],让p[i]达到最大值
        while (i - p[i] >= 0 && ManacherMatch(str[i - p[i]], str[i + p[i]])) {
            ++p[i];
        }
        // 3 跟新ct 和 r
        if (i + p[i] > r) {
            ct = i;
            r = i + p[i];
        }

        // 4 更新最长回文
        if (2 * p[i] - 1 > maxLen) {
            maxLen = 2 * p[i] - 1;
        }

    }
    return maxLen;
}

char str[maxn];

int main()
{
    cin >> str;
    int ans = Manacher(str);
    cout << ans / 2 << endl;
    // 请在此输入您的代码
    return 0;
}

代码练习 1 对应蓝桥云课 判定回文串 代码见下

cpp 复制代码
#include <iostream>
#include <cstring>
using namespace std;

#define maxn 1000010
#define split '$'
int p[maxn];
char strTmp[maxn];

void ManacherPre(char* str) {
    strcpy(strTmp, str);
    int i = 0;
    for (; strTmp[i]; ++i) {
        str[2 * i] = split;
        str[2 * i + 1] = strTmp[i];
    }
    str[2 * i] = split;
    str[2 * i + 1] = '\n';
}

bool ManacherMatch(char a, char b) {
    return a == b;
}

int Manacher(char* str) {
    ManacherPre(str);
    // ct : 当前已知的最优回文区域的中心位置
    int ct = 0;
    // r : 当前已知的最右回文区域的右边界(ct + p[ct])
    int r = 0;
    int maxLen = 1;
    // p[] : 记录每个位置的回文半径
    p[0] = 1;
    for (int i = 1; str[i]; ++i) {
        // 1 计算p[i]的初始值
        if (i < r) {
            p[i] = min(p[2 * ct - i], r - i);
        }
        else {
            p[i] = 0;
        }
        // 2 扩张p[i],让p[i]达到最大值
        while (i - p[i] >= 0 && ManacherMatch(str[i - p[i]], str[i + p[i]])) {
            ++p[i];
        }
        // 3 跟新ct 和 r
        if (i + p[i] > r) {
            ct = i;
            r = i + p[i];
        }

        // 4 更新最长回文
        if (2 * p[i] - 1 > maxLen) {
            maxLen = 2 * p[i] - 1;
        }

    }
    return maxLen;
}

char str[maxn];

int main()
{
    int n, m;
    cin >> n >> m;
    cin >> str;
    Manacher(str);
    while (m--) {
        int l, r;
        cin >> l >> r;
        l = l * 2 - 1;
        r = r * 2 - 1;
        int mid = (l + r) / 2;
        if (mid + p[mid] - 1 >= r) {
            cout << "Yes" << endl;
        }
        else {
            cout << "No" << endl;
        }
    }
    return 0;
}

代码练习2 小蓝的01串, 对应蓝桥云课 代码见下

cpp 复制代码
/*
1 首先,满足要求的子串一定是偶数长度的
反证法证明,假设是技术,见下:
原串 a     b     c
取反 1-a   1-b   1-c
反转 1-c   1-b   1-a
这时候,就会发现,1-b==b显然不合理,所以子串长度为奇数的话,一定是不合法
2 利用同样的方法,观察偶数的情况
原串 a     b      c     d
取反 1-a   1-b    1-c   1-d
反转 1-d   1-c    1-b   1-a
得出结论:a+d == 1  b+c == 1
3 这样一来,只需要把原先马拉车的模版,两边字符相等的逻辑,改成字符相加等于1即可
4 最后,统计偶数串的个数,做个简单的技术操作即可
*/

#include <iostream>
#include <cstring>
using namespace std;

#define maxn 1000010
#define split '$'
int p[maxn];
char strTmp[maxn];

void ManacherPre(char* str) {
    strcpy(strTmp, str);
    int i = 0;
    for (; strTmp[i]; ++i) {
        str[2 * i] = split;
        str[2 * i + 1] = strTmp[i];
    }
    str[2 * i] = split;
    str[2 * i + 1] = '\n';
}

bool ManacherMatch(char a, char b) {
  if(a == b){
    return a == split;
  }
  return (a - '0') + (b - '0') == 1;
}

int Manacher(char * str) {
    ManacherPre(str);
    // ct : 当前已知的最优回文区域的中心位置
    int ct = 0;
    // r : 当前已知的最右回文区域的右边界(ct + p[ct])
    int r = 0;
    int maxLen = 1;
    // p[] : 记录每个位置的回文半径
    p[0] = 1;
    for (int i = 1; str[i]; ++i) {
        // 1 计算p[i]的初始值
        if (i < r) {
            p[i] = min(p[2 * ct - i], r - i);
        }
        else {
            p[i] = 0;
        }
        // 2 扩张p[i],让p[i]达到最大值
        while (i - p[i] >= 0 && ManacherMatch(str[i - p[i]], str[i + p[i]])) {
            ++p[i];
        }
        // 3 跟新ct 和 r
        if (i + p[i] > r) {
            ct = i;
            r = i + p[i];
        }

        // 4 更新最长回文
        if (2 * p[i] - 1 > maxLen) {
            maxLen = 2 * p[i] - 1;
        }

    }
    return maxLen;
}

char str[maxn];

int main()
{
    int n;
    cin >> n;
    cin >> str;
    Manacher(str);
    long long ans = 0;
    for(int i=0; str[i]; i += 2){
      ans += p[i]/2;
    }
    cout << ans << endl;
    return 0;
}

代码练习3 反异或01串 对应蓝桥云课 代码见下

cpp 复制代码
/*
1 由于反异或操作只能进行一次,所以我们就恶意挑选其中一段来进行反异或操作,而剩下的段,统计1的个数就可以的
于是问题就变成"如何找到需要反异或的段"
2 考虑这种情况,先考虑奇数的情况
a b c d e
^ ^ ^ ^ ^
e d c b a
2 满足可以反异或的前提,中间这个位置必须是0,因为自己异或自己必然是0
然后由于异或满足交换律,也就是b*d = d*b,所以就变成了求回文串的情况了
同样偶数也是一样的,只不过偶数不需要判定中间为0的情况

3 利用manacher计算出每个位置作为回文串中心的最大半径p[i]
并且求区间中[i, i+p[i]-1]中1的个数,代表反异或操作之前需要的1
再求区间[i-p[i]+1, i+p[i]-1]以外的1的个数代表不进行反异或操作段中需要的1
把两者累加即可
*/

#include <iostream>
#include <cstring>
using namespace std;

#define maxn 2000010
#define split '$'
int p[maxn];
char strTmp[maxn];

void ManacherPre(char* str) {
    strcpy(strTmp, str);
    int i = 0;
    for (; strTmp[i]; ++i) {
        str[2 * i] = split;
        str[2 * i + 1] = strTmp[i];
    }
    str[2 * i] = split;
    str[2 * i + 1] = '\n';
}

bool ManacherMatch(char a, char b) {
    return a == b;
}

int Manacher(char* str) {
    ManacherPre(str);
    // ct : 当前已知的最优回文区域的中心位置
    int ct = 0;
    // r : 当前已知的最右回文区域的右边界(ct + p[ct])
    int r = 0;
    int maxLen = 1;
    // p[] : 记录每个位置的回文半径
    p[0] = 1;
    for (int i = 1; str[i]; ++i) {
        // 1 计算p[i]的初始值
        if (i < r) {
            p[i] = min(p[2 * ct - i], r - i);
        }
        else {
            p[i] = 0;
        }
        // 2 扩张p[i],让p[i]达到最大值
        while (i - p[i] >= 0 && ManacherMatch(str[i - p[i]], str[i + p[i]])) {
            ++p[i];
        }
        // 3 跟新ct 和 r
        if (i + p[i] > r) {
            ct = i;
            r = i + p[i];
        }

        // 4 更新最长回文
        if (2 * p[i] - 1 > maxLen) {
            maxLen = 2 * p[i] - 1;
        }

    }
    return maxLen;
}

char str[maxn];
int sum[maxn];

int getsum(int l, int r) {
    int pre = 0;
    if (l) {
        pre = sum[l - 1];
    }
    return sum[r] - pre;
}

int main()
{
    cin >> str;
    Manacher(str);
    int n = strlen(str);
    for (int i = 1; i < n; ++i) {
        sum[i] = sum[i - 1] + (str[i] == '1');
    }
    int ret = sum[n - 1];
    for (int i = 0; i < n; ++i) {
        if (i & 1) {
            //奇数位置,以数字为中心
            if (str[i] == '1') {
                //中间数不能为1
                continue;
            }
        }
        else {
            //偶数位置,以$为中心

        }
        //统计[i, i+p[i]-1]中1的个数
        int ans = getsum(i, i + p[i] - 1) +
            //在统计[i-p[i]+1, i+p[i]-1]以外1的个数
            getsum(0, n - 1) - getsum(i - p[i] + 1, i + p[i] - 1);
        ret = min(ans, ret);
    }
    cout << ret << endl;
    return 0;
}
相关推荐
csbysj202014 小时前
Chart.js 饼图:全面解析与实例教程
开发语言
歌_顿14 小时前
知识蒸馏学习总结
人工智能·算法
浩瀚地学15 小时前
【Java】常用API(二)
java·开发语言·经验分享·笔记·学习
程序员小寒15 小时前
从一道前端面试题,谈 JS 对象存储特点和运算符执行顺序
开发语言·前端·javascript·面试
七夜zippoe15 小时前
事件驱动架构:构建高并发松耦合系统的Python实战
开发语言·python·架构·eda·事件驱动
古城小栈15 小时前
Rust Trait 敲黑板
开发语言·rust
闲看云起15 小时前
LeetCode-day6:接雨水
算法·leetcode·职场和发展
没学上了15 小时前
VLM_一维离散卷积与二维离散卷积(还是复习感觉还行)
算法
HL_风神15 小时前
设计原则之迪米特
c++·学习·设计模式