对于一个01字符串,如果将这个字符串0和1取反后,再将整个串反过来和原串一样,就称作“反对称”字符串。比如00001111和010101就是反对称的,1001就不是。
现在给出一个长度为N的01字符串,求它有多少个子串是反对称的。
对于一个01字符串,如果将这个字符串0和1取反后,再将整个串反过来和原串一样,就称作“反对称”字符串。比如00001111和010101就是反对称的,1001就不是。
现在给出一个长度为N的01字符串,求它有多少个子串是反对称的。
第一行一个正整数N (N <= 500,000)。第二行一个长度为N的01字符串。
一个正整数,表示反对称子串的个数。
鸣谢 JZP
方法一:二分+哈希
枚举中间点,然后二分长度,用哈希判断是否满足反对称。
#include<iostream> #include<cstdio> #include<cstdlib> #include<cstring> #include<cmath> #include<algorithm> #define F(i,j,n) for(int i=j;i<=n;i++) #define D(i,j,n) for(int i=j;i>=n;i--) #define ll long long #define ull unsigned long long #define maxn 500005 #define base 233 using namespace std; int n,a[maxn],b[maxn]; ll ans; ull fa[maxn],fb[maxn],p[maxn]; char s[maxn]; inline ull hasha(int l,int r) { return fa[r]-fa[l-1]*p[r-l+1]; } inline ull hashb(int l,int r) { return fb[l]-fb[r+1]*p[r-l+1]; } int main() { scanf("%d",&n); scanf("%s",s+1); F(i,1,n) a[i]=s[i]-'0',b[i]=a[i]^1; F(i,1,n) fa[i]=fa[i-1]*base+a[i]; D(i,n,1) fb[i]=fb[i+1]*base+b[i]; p[0]=1;F(i,1,n) p[i]=p[i-1]*base; F(i,1,n-1) { int l=1,r=min(i,n-i),mid,tmp=0; while (l<=r) { mid=(l+r)>>1; if (hasha(i-mid+1,i+mid)==hashb(i-mid+1,i+mid)) tmp=mid,l=mid+1; else r=mid-1; } ans+=tmp; } printf("%lld\n",ans); return 0; }
方法二:manacher
令0=1,0≠0,1≠1,#=#,然后跑manacher。
#include<iostream> #include<cstdio> #include<cstdlib> #include<cstring> #include<cmath> #include<algorithm> #define F(i,j,n) for(int i=j;i<=n;i++) #define D(i,j,n) for(int i=j;i>=n;i--) #define ll long long #define ull unsigned long long #define maxn 1000005 using namespace std; int n,f[maxn]; ll ans; char ch[maxn],s[maxn]; inline bool judge(char a,char b) { return (a=='#'&&b=='#')||(a=='0'&&b=='1')||(a=='1'&&b=='0'); } inline void manacher() { int mx=0,id=0; F(i,1,n) { if (mx>i) f[i]=min(f[id*2-i],mx-i); else f[i]=0; while (judge(s[i-f[i]],s[i+f[i]])) f[i]++; if (i+f[i]>mx) mx=i+f[i],id=i; ans+=(f[i]>>1); } } int main() { scanf("%d",&n); scanf("%s",ch+1); s[1]='#'; F(i,1,n) s[i<<1]=ch[i],s[i<<1|1]='#'; n=n<<1|1; manacher(); printf("%lld\n",ans); return 0; }