您的位置:首页 > 其它

与非

2016-07-07 16:36 267 查看

题目大意



分类讨论

假若一个0左边有数,那么执行到0这里结果肯定为1。

只有左边没数的0和1与1与非会产生0,假若能统计得到的0的个数,便可以计算异或和。

连续一段1如果最左端有数,这一段有j个1,可以造成j/2上取整个0。

于是我们可以预处理前缀答案,还有一个位置往左最多扩展(是1即可扩展),有啥用呢?待会会讲。

l=1我们就可以直接输出了,必须判掉这种情况不然会有麻烦。

然后我们可以分类讨论:

如果第l位是0,那么就出现了一个0,而且因为是0,所以前面没有任何影响(即其不会存在于连续1中)。

l=r的话直接输出1。

l不等于r呢,0后面那段有多少个1我们需要知道,首先呢我们知道这一段的答案是可以用前缀和求出的,但这个不是对的,因为我们是把0当做了左边有数,即认为到了0这儿前缀与非就是1,但现实是0这儿前缀与非应该是0,所以应该减掉误的加上对的。

如果第l位是1,那么我们得到左边有j个1,l往右有k个1。

sum[r]=sum[l-1]-(j+1)/2+ans-k/2+(j+k+1)/2

ans=sum[r]-sum[l-1]+(j+1)/2+k/2-(j+k+1)/2

这样就是对的吗?不一定,如果往左一定延伸到了首部,那就出了问题,因为我们会认为前面有一个0让这一段1靠着,所造成的影响就是除以2上取整,但现在没有这个0,一开始就是1,就应该是除以2下取整。

只有这种情况会错吗?不一定,还有你虽然没延伸到头,但你延伸的位置所靠的0就是头,我们以为到0这里前缀与非是1,但这里实际是0。

于是这就是所有情况,求往左延伸直接使用left数组,求往右延伸可以二分然后利用left数组来判定。

最后吐槽一句,打这种方法好烧脑……

#include<cstdio>
#include<algorithm>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
const int maxn=4000000+10;
int a[maxn],sum[maxn],num[maxn],left[maxn];
int i,j,k,l,r,t,n,m,top,ans;
bool czy;
int read(){
int x=0,f=1;
char ch=getchar();
while (ch<'0'||ch>'9'){
if (ch=='-') f=-1;
ch=getchar();
}
while (ch>='0'&&ch<='9'){
x=x*10+ch-'0';
ch=getchar();
}
return x*f;
}
int binary(int x,int y){
int l=0,r=y,mid;
while (l<r){
mid=(l+r+1)/2;
if (left[x+mid-1]>=mid) l=mid;else r=mid-1;
}
return l;
}
int main(){
czy=1;
n=read();
while (n--){
t=read();
if (t==1){
j=read();
if (czy) j^=ans;
a[++top]=j;
num[top]=top==1?a[top]:!(num[top-1]&a[top]);
sum[top]=sum[top-1]+(!num[top]);
left[top]=(j?left[top-1]+1:0);
}
else{
l=read();r=read();
if (czy&&ans){
l=top-l+1;
r=top-r+1;
swap(l,r);
}
if (l==1) ans=sum[r];
else{
if (!a[l]){
if (l==r) ans=1;
else{
j=binary(l+1,r-l);
ans=sum[r]-sum[l-1]-(j+1)/2+(j+2)/2;
}
}
else{
j=left[l-1];
k=binary(l,r-l+1);
ans=sum[r]-sum[l-1]+(j+1)/2+k/2-(j+k+1)/2;
if (j==l-1||j==l-2&&a[1]==0) ans=ans+(j+k+1)/2-(j+k)/2-(j+1)/2+j/2;
}
}
ans=(r-l+1-ans)%2;
printf("%d\n",ans);
}
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: