LOJ#2452. 「POI2010」反对称 Antisymmetry

题目描述

对于一个 (0/1) 字符串,如果将这个字符串 (0)(1) 取反后,再将整个串反过来和原串一样,就称作「反对称」字符串。比如 (00001111)(010101) 就是反对称的,而 (1001) 就不是。
现在给出一个长度为 (n)(0/1) 字符串,求它有多少个子串是反对称的,注意这里相同的子串出现在不同的位置会被重复计算。

输入格式

第一行一个正整数 (n)
第二行一个长度为 (n)(0/1) 字符串。

输出格式

一行一个整数,表示原串的反对称子串个数。

样例

样例输入

8
11001011

样例输出

7

数据范围与提示

对于 (100\%) 的数据, (1le nle 500 000)

Translated by vincent163

题解

马拉车也是可以做的而且复杂度更优。
我还是菜啊没有看出来单调性,这个取反翻转操作是单调的,你一个大的串如果是反对称串,中间的任何一个也肯定是。因为翻转的位置并没有变...
那么枚举起点,二分子串长度判断即可。
因为取反后翻转这个操作,所以不可能有奇数串符合条件(奇数串取反后翻转了一定不相等),所以判断也很好写
然后比较坑的就是答案要用longlong存,我因为这个爆了好几发,一开始还以为是进制数选的不好。
复杂度(O(nlogn))

#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;
typedef long long ll;
const int N = 500010;
const ull base = 13131;

ull h1[N], h2[N], p[N];
int n;
ll ans = 0;
char s[N];

ull get_h1(int l, int r) { return h1[r] - h1[l - 1] * p[r - l + 1]; }
ull get_h2(int l, int r) { return h2[l] - h2[r + 1] * p[r - l + 1]; }

int check(int x) {
	int l = 1, r = min(x, n - x);
	while(l <= r) {
		int mid = (l + r) >> 1;
		if(get_h1(x - mid + 1, x) == get_h2(x + 1, x + mid)) l = mid + 1;
		else r = mid - 1;
	}
	return r;
}

int main() {
	scanf("%d%s", &n, s + 1); p[0] = 1;
	for(int i = 1; i <= n; ++i) p[i] = p[i - 1] * base, h1[i] = h1[i - 1] * base + (ull)s[i];
	for(int i = n; i; --i) h2[i] = h2[i + 1] * base + (ull)(s[i] == '0' ? s[i] + 1 : s[i] - 1);
	for(int i = 1; i < n; ++i) ans += check(i);
	printf("%lld
", ans);
}
原文地址:https://www.cnblogs.com/henry-1202/p/10321013.html