洛谷题目链接
WOJ题目链接
题目
洛谷 [POI2010]P3501 ANT-Antisymmetry
WOJ#3818 反对称 Antisymmetry
题目描述
对于一个01字符串,如果将这个字符串0和1取反后,再将整个串反过来和原串一样,就称作“反对称”字符串。比如00001111和010101就是反对称的,1001就不是。现在给出一个长度为N的01字符串,求它有多少个子串是反对称的。
输入
第一行一个正整数 n。第二行一个长度为 n 的 0/1 字符串。
输出
一行一个整数,表示原串的反对称子串个数。
样例
- 输入样例
8
11001011 - 输出样例
7
题意
定义一种01子串,其中每个位置上的值,与它关于中央对称后的位置上的值相反。求给出的字符串中,这种子串的个数。
思路
首先我们来研究一下这种子串的性质。由于0/1始终不能与自己相反,故子串并不存在中央位置,也就是说,子串长度必为偶。其次,由题目分析可知每个位置上的值都与它关于中央对称的位置上的值相反。然后……
嘛,先不管那么多啦!既然没什么思路(误),那我们先愉快地考虑暴力吧!说不定不知什么时候就想出正解了呢!
既然对称的两点相反,那我们就枚举中点,再判断就行了。
以下,便是博主的第一份暴力代码的核心程序明知道过不到偏就要交上去玩的 。不过别这么说嘛,暴力也很考技术的(误)。
for(int i=1;i<n;i++)
{
l=i,r=i+1;
while(l>=1&&r<=n)
{
if(a[l]==(a[r]^1)) {ans++;l--,r++;}
else break;
}
}
不得不说,这份代码的效果远高于博主的预期。在下原以为只能过那么……五六个点吧,结果过了四十九个!剩下的全T了——谁叫一共有六十二个点呢。
不过在暴力的过程中,我们也产生了一些新思路——如果用哈希来判断,能否更优?这样的话,考虑到中点的这样的一个状况,就需要向左和向右分别处理两次哈希,将
s
[
i
]
s[i]
s[i]的哈希值分别存于
h
a
s
h
l
[
i
]
hash_l[i]
hashl[i]和
h
a
s
h
r
[
i
]
hash_r[i]
hashr[i]内。
为了进一步优化,在循环过程中可以采用二分答案。那么这道题也就这样解决啦。
代码
#include<iostream>
#include<cstring>
#include<algorithm>
const int maxn=5e5+10;
const unsigned long long base=1e9+7;
using namespace std;
int n,ans;
unsigned long long power[maxn],hash_l[maxn],hash_r[maxn];
char s[maxn];
bool check(int l,int r,int x)
{
unsigned long long s1,s2;
int t1=l+x-1,t2=r+x-1;
s1=hash_l[t1]-hash_l[l-1];
s2=hash_r[t2]-hash_r[r-1];
if(l>r)
{
swap(s1,s2);
swap(l,r);
}
s1*=power[r-l];
if(s1==s2) return 1;
else return 0;
}
int main()
{
cin>>n;
cin>>s;
power[0]=1;
for(int i=1;i<=n;i++) power[i]=power[i-1]*base;
for(int i=1;i<=n;i++) hash_l[i]=hash_l[i-1]+s[i-1]*power[i];//从左向右的哈希
for(int i=1;i<=n;i++) hash_r[i]=hash_r[i-1]+(s[n-i]^1)*power[i];//从右向左的哈希
for(int i=2;i<=n;i++)
{
int k=n-i+2,l=1,r=min(n-i+1,i-1);
int tot=0;
while(l<=r)
{
int mid=(l+r)>>1;
if(check(i,k,mid)) tot=max(mid,tot),l=mid+1;
else r=mid-1;
}//二分答案
ans+=tot;
}
cout<<ans;
}