[题解] Antisymmetry 哈希+二分
粗略题意:给你一串只包含0和1的字符串,长度
n
≤
500000
n\le500000
n≤500000,求满足条件的子串个数。
如果子串
s
[
i
,
j
]
s[i,j]
s[i,j]满足条件,那么这个子串长度
l
l
l是偶数,并且第一个^ 最后一个=1,第二个^倒数第二个 = 1…
首先想一波暴力解法,枚举中点和长度,逐个统计,时间复杂度
O
(
n
2
)
O(n^2)
O(n2)。
然后发现对于固定的中点,它的长度是可以二分的,并且利用hash,check函数也很好写。
这里主要是对于反串的处理。我们从n~1构造反哈希:
g
[
i
]
=
g
[
i
+
1
]
∗
P
+
s
[
i
]
g[i] = g[i+1]*P+s[i]
g[i]=g[i+1]∗P+s[i]
相比于原来的哈希
h
[
i
]
=
h
[
i
−
1
]
∗
P
+
s
[
i
]
h[i] = h[i-1]*P+s[i]
h[i]=h[i−1]∗P+s[i]
有个特点:反哈希最高位在右边,而哈希最高位在左边。
对于反哈希,它的子段计算公式类似:
H
a
s
h
(
l
,
r
)
=
g
[
l
]
−
g
[
r
+
1
]
∗
p
[
r
−
l
+
1
]
Hash(l,r) = g[l] - g[r+1]*p[r-l+1]
Hash(l,r)=g[l]−g[r+1]∗p[r−l+1]。
下面给出代码,注意需要判断以这个点为中点是否能产生满足条件的串。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
#include<vector>
#include<map>
#include<queue>
#include<stack>
#define ll long long
#define ull unsigned long long
#define pii pair<int,int>
using namespace std;
const double eps = 1e-10;
const double pi = acos(-1.0);
const int maxn = 5e5 + 10;
const ull P = 131;
int n;
ull h[maxn],p[maxn];
ull g[maxn];//预先处理出字符串"反串"的哈希值
char s[maxn];
inline ull Hash1(int l, int r){return h[r] - h[l-1]*p[r-l+1];}
inline ull Hash2(int l, int r){return g[l] - g[r+1]*p[r-l+1];}
bool check(int mid, int len){//len为字符串的半径
int l = mid - len + 1, r = mid + len;
if(l < 1 || r > n) return 0;
return Hash1(l,mid) == Hash2(mid+1,r);
}
void solve(){
p[0] = 1;
for(int i = 1; i < maxn; i++) p[i] = p[i-1] * P;
scanf("%d%s",&n,s+1);
for(int i = 1; i <= n; i++) h[i] = h[i-1]*P + (ull)s[i];
for(int i = 1; i <= n; i++) s[i] = (s[i]-'0')^1+'0';
for(int i = n; i >= 1; i--) g[i] = g[i+1]*P + (ull)s[i];
ll ans = 0;
for(int m = 1; m < n; m++){
int l = 0, r = n/2;
bool ok = 0;
while(l < r){//枚举半径
int mid = l + r + 1 >> 1;
if(check(m,mid)){
ok = 1;
l = mid;
}
else r = mid - 1;
}
if(ok)ans += (ll)l;
}
printf("%lld",ans);
}
int main()
{
solve();
return 0;
}