简要题意

定义:如果一个 \(01\) 串是“反对称”的,当且仅当将其每一位取反后翻转,与原来的串相同。

给出一个长度为 \(n\)\(01\) 序列。求其有多少个子串是“反对称”的。

\(1 \leq n \leq 5\times10^5\)

思路

经典的 Manacher 练习题。

考虑到“反对称”的定义与回文的定义很像。只是一个是两边的字符相同,一个是两边的字符异或和为 \(1\)

于是我们可以修改 Manacher 模板来实现。

统计答案时,设 \(p_i\) 为以 \(i\) 为对称中心,我们求出来的最长循环半径长度。则其实际为 \(\lfloor p_i\div2\rfloor\)(去掉 # 的干扰)。累加起来即可。

注意本题几个坑点(感谢 这个帖子):

  • Manacher 构建新字符串时是 \(i=i+2\)。应为奇数长度的“反对称”串不存在(中心不存在,因为不存在一个数,使得其取反等于其本身)。但是可能会被 # 误导,从而被错误地计算。
  • 十年 OI 一场空,不开 long long 见祖宗。

代码

#include <bits/stdc++.h>
using namespace std;

int n;
string str;

const int N = 2e6+5;

string pre(string s){
    if(s.size()==0) return "^$";
    string ret="^";
    for(int i=0;i<s.size();i++){ret+='#';ret+=s[i];}
    return ret+="#$";
}

int p[N];

bool XOR(char a,char b){
    return (a=='1'&&b=='0')||(a=='0'&&b=='1')||(a=='#'&&b=='#');
}

long long manacher(string str){
    int n=str.size(),c=0,r=0;
    long long ans=0;
    for(int i=1;i<n;i+=2){
        int j=(c<<1)-i;
        if(r>i) p[i]=min(r-i,p[j]);
        else {p[i]=0;}
        while(XOR(str[i+p[i]+1],str[i-p[i]-1])) p[i]++;
        if(i+p[i]>r){c=i;r=i+p[i];}
        ans+=p[i]>>1;
    }
    return ans;
}

int main(){
    cin>>n>>str;
    cout<<manacher(pre(str));
    return 0;
}