P3501 [POI2010] ANT-Antisymmetry 反对称 题解(字符串哈希+二分)

原题

题意

若一个由 01 01 01组成的字符串将 0 0 0 1 1 1取反,并倒过来后与原字符串相同,则称为反对称字符串。现在给你一个长度为 n ( n ≤ 1 0 5 ) n(n \le 10^5) n(n105) 01 01 01组成的字符串,求它有多少个反对称子串。(两个子串只要起始位置不同或长度不同就算做两个子串,即使子串的内容相同)

样例

8
11001011
7

思路

本文没有详细讲解字符串哈希的实现,不会戳这里

朴素

先预处理出原字符串的 H a s h Hash Hash数组和原字符串 01 01 01取反并前后颠倒的 H a s h Hash Hash数组,然后枚举每个子串,判断两个 H a s h Hash Hash是否相等,计数。复杂度 O ( n 2 ) O(n^2) O(n2),喜提TLE。

进阶

(此时我偷偷看了一下题目的标签,要用二分) 先考虑如何构造一个反对称字符串,如果没有“反”,那么就是回文串,很明显,是从中间开始,向两边扩展,对应位置的 01 01 01相同,例如: 01010 01010 01010 11100111 11100111 11100111。但是要分成两种情况,奇数长度的字符串和偶数长度的字符串构造方法略有不同。可是有“反”,也就是说对应位置不是相同,而是相反。带到样例里算一下,比如说 001011 001011 001011,构造过程如下: 10 10 10 -> 0101 0101 0101 -> 001011 001011 001011。进一步发现,每一步都会构成一个反对称字符串,这就给二分提供了基础。

实现

可以枚举每个反对称子串中间两个位置的右边那个(这里需要注意一个细节,其实完全不用分两种情况,因为对应位置的字符不同,如果长度是奇数,中间的字符不可能和自己不同,所以长度只能是偶数),用二分找到最大的反对称字符串长度(实际的长度是 2 ⋅ m i d 2·mid 2mid,这里枚举半边的长度),判断时看 i − m i d i-mid imid i + m i d − 1 i+mid-1 i+mid1的子串是否是反对称串,如果是,就可以继续扩大长度判断,如果不是,由于中心确定,已经发现对应位置上的字符相同,不符合要求,再向两边扩大长度也不会成为反对称串,所以缩小长度。最后计数器加上的值是 r r r,以 i i i为中心,长度是 2 2 2 2 r 2r 2r中的偶数都是符合标准的。

代码

(本人为了保险,写了两个哈希表,其实一个也能过)

#include<bits/stdc++.h>
using namespace std;
#define mod1 1000000007
#define mod2 1000000009
#define mul 2
int n,a[500005];
long long h1[500005],h2[500005],g1[500005],g2[500005];
//h是原字符串,g是取反翻转后的
long long m1[500005],m2[500005],ans;
char s[500005];
int main(){
	scanf("%d%s",&n,s+1);
	m1[0]=m2[0]=1;
	for(int i=1;i<=n;i++) m1[i]=m1[i-1]*mul%mod1;
	for(int i=1;i<=n;i++) m2[i]=m2[i-1]*mul%mod2;
	for(int i=1;i<=n;i++) h1[i]=(h1[i-1]*mul%mod1+(s[i]=='1'))%mod1;
	for(int i=1;i<=n;i++) h2[i]=(h2[i-1]*mul%mod2+(s[i]=='1'))%mod2;
	for(int i=n;i>=1;i--) g1[i]=(g1[i+1]*mul%mod1+(s[i]=='0'))%mod1;
	for(int i=n;i>=1;i--) g2[i]=(g2[i+1]*mul%mod2+(s[i]=='0'))%mod2;
	for(int i=1;i<=n;i++){
		int l=1,r=min(i-1,n-i+1),flag=0;
		while(l<=r){
			int mid=(l+r)/2;
			int t1=(h1[i+mid-1]-h1[i-mid-1]*m1[2*mid]%mod1+mod1)%mod1,
			t2=(h2[i+mid-1]-h2[i-mid-1]*m2[2*mid]%mod2+mod2)%mod2,
			tt1=(g1[i-mid]-g1[i+mid]*m1[2*mid]%mod1+mod1)%mod1,
			tt2=(g2[i-mid]-g2[i+mid]*m2[2*mid]%mod2+mod2)%mod2;
			if(t1==tt1&&t2==tt2) l=mid+1,flag=1;
			else r=mid-1;
		}
		ans+=r;
	}
	printf("%lld\n",ans);
	return 0;
}

附赠如何见到祖宗的指南:
在这里插入图片描述

  • 21
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值