Antisymmetry(反对称)——Manacher

Antisymmetry

题目描述

  • 对于一个01字符串,如果将这个字符串0和1取反后,再将整个串反过来和原串一样,就称作“反对称”字符串。比如00001111和010101就是反对称的,1001就不是。
  • 现在给出一个长度为N的01字符串,求它有多少个子串是反对称的。

输入格式

  • 第一行一个正整数N ((Nle 5 imes 10^5))。第二行一个长度为N的01字符串。

输出格式

  • 一个正整数,表示反对称子串的个数。

样例输入

8
11001011

样例输出

7

hint

  • 7个反对称子串分别是:01(出现两次), 10(出现两次), 0101, 1100和001011

Solve

  • 题目大意
    • 求出反对称子串的数量,常说的回文是说从中间开始,两边的字符都相同,反对称就是不相同
  • 这道题要求反对称子串,自然想到 (O(n)) 求回文字串的 mancher 算法,因为反对称和回文的定义很相像。
void Manacher() {//Manacher板子
    s[0] = '~';//首位插入特殊字符
    for (int i = 1; i <= n; ++i)
        s[i*2-1] = '#', s[i<<1] =a[i];//隔空插入特殊字符,判奇回文串
    s[n*2+1] = '#';
    for (int i = 1, c = 0, r = 0; i <= n << 1; ++i) {
        f[i] = i <= r ? min(f[c*2-i], r - i + 1) : 1;
        while (s[i-f[i]] == s[i+f[i]]) ++f[i];//f[i]*2-1就是以i为中心最长回文子串的长度
        if (i + f[i] - 1 > r) r = i + f[i] - 1, c = i;
    }
}
  • mancher 算法求的是奇回文串,本题要求的都是偶数长度的反对称子串,我们可以变化一下,从每两个字符的中间位置出发进行更新,这样就不需要插入特殊字符了。
  • 接下来说一些代码实现的细节,因为这道题并不是裸的板子,需要一些小小的变形,自己画画图有助于理解。
    • 在初始赋值的时候,到右边界最长的字符数应为 r-i,注意这里的 i 是第 i 个空隙。
    • 扩展的时候,左边界是 i-f[i],右边界是 i+f[i]+1,这里需要注意的是我这种写法不能通过在字符串首位放入特殊字符来自行判断边界,需要判断一下,还有这里求的就是不相同!=的字符,而不是板子里的==了。
    • 更新最长右边界的时候,此时的右边界是 i+f[i]。
    • 计算答案的时候,f[i] 存的是向一边扩展的最大长度,f[i]*2 是以第 i 个间隔为中心的最长反对称子串长度,而这个间隔的反对称子串数量其实就是长度的一半,也就是 f[i]。
  • 最后提醒一点,n 为 5e5 的01串的子串数量是 1e10 级别的,要开long long。

Code

#include <cstdio>
#include <algorithm>
#define int long long//记得开long long!!
using namespace std;
const int N = 5e5 + 5;
int n, f[N], ans;
char a[N];
signed main() {
    scanf("%lld%s", &n, a + 1);//读入总长及01串
    for (int i = 1, c = 0, r = 0; i < n; ++i) {
    //i是第i个间隔,r是最远右边界,c是其中心的位置
        if (i < r) f[i] = min(f[c*2-i], r - i);
        //进行初始化,不满足i<r的初始值就是0
        while (i - f[i] > 0 && i + f[i] + 1 <= n && a[i-f[i]] != a[i+f[i]+1]) f[i]++;//要判边界
        if (i + f[i] > r) r = i + f[i], c = i;//更新最远右边界及其中心
        ans += f[i];//统计答案
    }
    printf("%lld
", ans);
    return 0;
}
原文地址:https://www.cnblogs.com/shawk/p/13401684.html