SP15569 STC02 - Antisymmetry 题解
0x00 思路
先看题。
「对于一个 \(01\) 字符串,如果将这个字符串 \(0\) 和 \(1\) 取反后,再将整个串反过来和原串一样,就称作「反对称」字符串。比如 \(00001111\) 和 \(010101\) 就是反对称的,而 \(1001\) 就不是。 现在给出一个长度为 \(n\) 的 \(01\) 字符串,求它有多少个子串是反对称的,注意这里相同的子串出现在不同的位置会被重复计算。」
emmm……这题……和 P3498 [POI2010]KOR-Beads 有什么本质上的区别吗?都是运用字符哈希将一个序列正着存一遍再反着存一遍,只不过这道题需要取反而已,然后枚举所有子串即可。
0x01 优化
QAQ

接着你会发现它 TLE 了。
那么就说明如果我们使用完全暴力的方法是不可行的。这个时候我们就需要一点点的数学分析。
首先,我们发现一个 \(01\) 串如果是回文的,那么它的左右两边各减去 \(i\) 个字母(\(0\le i \le \left \lfloor \dfrac{len}{2} \right \rfloor\))也都是回文的。那么我们就可以找到以某一个位置作为对称轴的最远的一个符合反对称条件的字符串,接着我们就可以找到一此位置作为对称轴的反对称子串的个数。
那么有人可能会想:是否需要分奇偶数的情况?
其实是不用的。
为什么?
因为长度为奇数的字符串肯定不是反对称的。因为取反过后 \(0\) 的个数一定和去反前 \(0\) 的个数不同,所以不可能重叠。
接着,我们根据上面推论的东西可以发现子串是单调的。所以我们可以使用二分进行优化。那么致辞,这道题也就被我们轻轻松松地 A 了。
code
不开 long long 见祖宗!!!
#include<map>
#include<cstdio>
#include<string>
#include<cmath>
#include<cstring>
char space[500005],space2[500005];//原数组,取反后数组
long long ans;
unsigned long long Hash[500005],p[500005],Hash2[500005];//原哈希,,取反后反过来哈希
const int clac=27796511;//乘数
int l,r;
inline void qr(int &ret){int x=0,f=0;char ch;ch=getchar();while(ch<'0'||ch>'9')f|=ch=='-',ch=getchar();while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+(ch^48),ch=getchar();ret=f?-x:x;return;}
//快读,别翻了
unsigned long long getnum(int l,int r)//原哈希函数
{
return Hash[r]-Hash[l-1]*p[r-l+1];
}
unsigned long long getnum2(int l,int r)//现函数
{
return Hash2[l]-Hash2[r+1]*p[r-l+1];
}
int main()
{
int i,n,q,tot,j;
p[0]=1;
for(i=1;i<=500000;i++)
p[i]=p[i-1]*clac;
qr(n);
scanf("%s",space);//当做字符串处理
for(i=1;i<=n;i++)
{
Hash[i]=Hash[i-1]*clac+space[i-1];
space2[i-1]=!(space[i-1]-'0')+'0';
}
for(i=n;i;--i)
Hash2[i]=Hash2[i+1]*clac+space2[i-1];
for(i=1;i<n;i++)//对称轴(即中心)在哪
{
int l=0,r=std::min(i,n-i);//防止越界
while(l<r)//二分
{
unsigned long long sum=getnum(i-r+1,i+r),sumx=getnum2(i-r+1,i+r);//该子串哈希值
if(l+1==r)
{
if(sum==sumx)
l=r;
break;
}
int mid=(l+r)/2;
sum=getnum(i-mid+1,i+mid),sumx=getnum2(i-mid+1,i+mid);
if(sum==sumx)
l=mid;
else
r=mid-1;
}
ans+=l;//子串个数
}
printf("%lld",ans);
return 0;
}

浙公网安备 33010602011771号