刚刚学习了manacher算法,是在O(n)时间内求以每个字符或每两个字符为中心的最长回文子串长度的一个算法。
然后做了一道POI的题,一道好题。看了题解再做的,有点可惜这道题了。
Description
对于一个01字符串,如果将这个字符串0和1取反后,再将整个串反过来和原串一样,就称作“反对称”字符串。比如00001111和010101就是反对称的,1001就不是。
现在给出一个长度为N的01字符串,求它有多少个子串是反对称的。
Input
第一行一个正整数N (N <= 500,000)。第二行一个长度为N的01字符串。
Output
一个正整数,表示反对称子串的个数。
Sample Input
8
11001011
Sample Output
7
HINT
7个反对称子串分别是:01(出现两次), 10(出现两次), 0101, 1100和001011
1 #include<cstdio>
2 #include<iostream>
3 #include<cstring>
4 #include<cmath>
5 #define mxn 1000010
6 using namespace std;
7 int str[mxn],tmp[mxn],f[mxn];
8 char s[mxn];
9 void init(int len){
10 for(int i=0;i<len;i++)
11 str[i]=s[i]-'0';
12 tmp[0]=3;
13 for(int i=1;i<=2*len;i+=2){
14 tmp[i]=2;
15 tmp[i+1]=str[i/2];
16 }
17 tmp[2*len+1]=2;
18 tmp[2*len+2]=3;
19 }
20 bool check(int x,int y){
21 return ((tmp[x]^tmp[y])==1)||(tmp[x]==2&&tmp[y]==2);
22 }
23 void manacher(int len){
24 int mx=0,po=0;
25 for(int i=1;i<=len;i+=2){
26 if(mx>i)
27 f[i]=min(mx-i,f[2*po-i]);
28 else
29 f[i]=1;
30 while(check(i-f[i],i+f[i]))
31 f[i]++;
32 if(f[i]+i>mx){
33 mx=f[i]+i;
34 po=i;
35 }
36 }
37 long long ans=0;
38 for(int i=1;i<=len;i+=2)
39 ans+=(f[i]-1)>>1;
40 printf("%lld",ans);
41 }
42 int main(){
43 int n;scanf("%d",&n);
44 scanf("%s",s);init(n);manacher(2*n+1);
45 return 0;
46 }
仔细考虑一个反对称子串的性质:
1、反对称子串一定是偶数串。因为不论0还是1,取反后都不会和自己相等。对于一个奇数串,正中间的数取反对称还是和自己是对应。
2、一个串是反对称串,它的偶数子串也一定是反对称串。显然。
3、以正中间两个数中间的空为对称轴,整个反对称串一定是0与1相对的。
那么就很容易想到,跑一遍manacher,但求得不是回文串,而是反对称串。只要把判断两个字符是否相等改为判断两个数是否互反即可。如果碰到‘#’就还是需要相等。
预处理往串里填‘#’还是需要的,而且这回只需要跑以‘#’为中心的串了。因为只有以‘#’为中心的串在原串中才是偶数串。
*不仅是只需要跑‘#’,而且是只能跑‘#’。0,1为中心是不能跑的,跑了会出错。
ans的求解就是每个‘#’的f(rad)值减一除以二求和。
复杂度就是O(n)。
2017-06-29