A N T − A n t i s y m m e t r y ANT-Antisymmetry ANT−Antisymmetry
题目链接:luogu P3501 & luogu SP15569
题目
对于一个 01 01 01字符串,如果将这个字符串 0 0 0和 1 1 1取反后,再将整个串反过来和原串一样,就称作“反对称”字符串。比如 00001111 00001111 00001111和 010101 010101 010101就是反对称的, 1001 1001 1001就不是。
现在给出一个长度为 N N N的 01 01 01字符串,求它有多少个子串是反对称的。
输入
第一行,一个数字
N
N
N,表示
01
01
01字符串的长度。
第二行,一个长度为
N
N
N的
01
01
01字符串。
输出
输出一个数字,就是所要求的答案。
样例输入
8
11001011
样例输出
7
样例解释
7 7 7个反对称子串分别是: 01 01 01(出现两次), 10 10 10(出现两次), 0101 0101 0101, 1100 1100 1100和 001011 001011 001011
数据范围
n ≤ 5 × 1 0 5 n≤5×10^5 n≤5×105
思路
这道题可以用哈希
+
+
+二分的方法来做。
就是由题意我们可以看出,这些反对称子串的长度必须是双数。
那我们可以枚举每一个点,把它当成反对称子串的左中心,二分以此点为左中心的反对称子串数量。
最后,我们把这些数量统计起来,加起来,就是我们要的答案了。
代码
#include<cstdio>
#define ll long long
#define ull unsigned long long
#define min(x, y) (x) < (y) ? (x) : (y)
using namespace std;
ll n, ans, p = 7 , P[500001], c[500001], d[500001];
char a[500001], b[500001];
ll work(ll i) {//二分
ll l = 0, r = min(i + 1, n - i - 1);
while (l < r) {
ll mid = (l + r + 1) >> 1;
if ((ull)(c[i] - c[i - mid] * P[mid]) == (ull)(d[i + 1] - d[i + 1 + mid] * P[mid])) l = mid;
else r = mid - 1;
}
return l;
}
int main() {
scanf("%lld", &n);//读入
getchar();//处理换行符
scanf("%s", &a);//读入
for (ll i = 0; i < n; i++)//求出取反后的字符串
if (a[i] == '0') b[i] = '1';
else b[i] = '0';
P[0] = 1;//初始化
for (ll i = 1; i <= n; i++)//初始化
P[i] = P[i - 1] * p;
for (ll i = 0; i < n; i++)//求出未取反和取反的字符串的哈希值
c[i] = c[i - 1] * p + a[i] - 36;
for (ll i = n - 1; i >= 0; i--)
d[i] = d[i + 1] * p + b[i] - 36;
for (ll i = 0; i < n - 1; i++) {//枚举反对称子串的中心
if (a[i] == a[i + 1]) continue;//没有反对称的
ans += work(i);//二分以此点为中心的反对称子串数量
}
printf("%lld", ans);//输出
return 0;
}