BZOJ 4017: 小Q的无敌异或【异或的和,和的异或】

题目链接:

https://www.lydsy.com/JudgeOnline/problem.php?id=4017
https://acm.njupt.edu.cn/problem/BZOJ4017

第一问:求所有区间异或的和

xo[i]xo[i]表示前ii个数的异或值,sum[i]sum[i]表示前ii个数的和
第一问:用cnt[k][0]cnt[k][1]cnt[k][0],cnt[k][1]表示第kk00的个数和11的个数,朴素的做法是枚举LLRRO(n2)O(n^2)
我们先枚举RR,如果sum[R]sum[R]的第kk位是11,要是有个sum[L1]sum[L-1]的第kk位是00就好啦~,那我们的这个sum[L R]sum[L~R]区间的第kk位就是11了,我们要做的就是统计出每枚举的一个sum[R]sum[R]有多少个sum[L1]sum[L-1]与他异或是11的,而且不需要知道具体是哪几个LL,只需要知道有多少个,那么个数就是cntcnt里面保存的,是11就找00,是00就找11

第二问:求所有区间和的异或

第二问其实就是bzoj 4888以及洛谷3760,阔以用来单独用来练习

思路就是统计 sum[r]-sum[l-1] 第k位为1 的区间有多少个,如果是奇数个的话那么最后异或起来这一位就是1,对答案就有贡献。但是问题是怎么快速统计喃???

分析一哈,如果要相减之后第k位还是1 的话,那么就是大概以下4种情况,分别用①②③④来表示
在这里插入图片描述

当sum[r]的第k为是1的时候

就是图①②这个两种情况:

图①:不会发生借位的情况

只看绿色框框里的低位,sum[r]>=sum[l-1]
相减的时候够减,第k位不会向高位借,所以直接就是1-0=1的意思来产生1了

图②:会发生借位的情况

只看绿色框框里的低位,sum[r]<sum[l-1]
这种情况就是不够减,肯定会发生借位产生1
我们这里是只看了绿色框框里的低位,小减大不是减成负数了蛮???但其实不会,因为高位低位一起看的话 sum[r]总是大于sum[l-1]的得哇,所以不用担心产生负数

当sum[r]的第k为是0的时候

第k位是0,那肯定会产生借位的情况,不同的是,是直接从高位借还是从低位慢慢一路借过来喃

图③:直接从高位借位的情况

这个情况是sum[l-1]的第k位是1,而sum[r]的是0,随便怎样都比不过他大,所以只能直接从高位借

图④:从低位慢慢借过来的情况

当sum[r]的第k为是0的时候

两个的第k位都是0,所以只有sum[r]的低位<sum[l-1]的低位的时候才会向高位借

所以主要思路就是:分类讨论第k位是1和0的情况,然后找个方法能快速知道小于某个数的有多少个,并且这个方法是单点修改的,所以树状数组就非常符合
重新回味这道题发现好像bzoj没了???不过洛谷3760还能做

#include"bits/stdc++.h"
using namespace std;
typedef long long LL;
const int maxn=1e6+5;
const int MOD=1e9+7;
int tree[2][maxn];
int sum[maxn],P[maxn];//P数组是用来保存只需要的低位 
int N;
void Add(int pos,int v,int cmd)
{
	if(pos<1)return ;
	for(int i=pos; i<=N; i+=i&-i)tree[cmd][i]+=v;
}
int getsum(int pos,int cmd)
{
	int res=0;
	for(int i=pos; i>0; i-=i&-i)res+=tree[cmd][i];
	return res;
}
int solve()
{
	
	int res=0;
	for(int k=0; k<=20; k++)
	{
		int cnt[2]={0};//记录第k为是1和是0的个数 
		int tp=0;
		memset(tree,0,sizeof tree);
		for(int i=1; i<=N; i++)P[i]=sum[i]%(1<<(k));		//不包括第k位,只是第k位右边的低位,所以这里不是%(1<<(k+1)) 
		sort(P+1,P+1+N);
		int n=unique(P+1,P+1+N)-(P+1);						//用来去重 
		for(int r=1; r<=N; r++)								//枚举sum[r] 
		{
			
			int cmd=(sum[r]>>k)&1;								//看这个数的第k位是1还是0 
			cnt[cmd]++;										//0或1的个数++ 
			int now=sum[r]%(1<<k);							//now就是这个sum[i]的低位
			int pos=lower_bound(P+1,P+1+n,now)-P;			//在保存的低位中寻找,lower_bound是找 第一个>=某个数的位置,返回的是迭代器 
			
			int less_0=getsum(pos,0);						//得到sum[l-1]第k位是0,且低位<=sum[r]的低位,对应情况① 
			int less_1=getsum(pos,1);						//得到sum[l-1]第k位是1,且低位<=sum[r]的低位,对应情况③ 
			
			int more_0=cnt[0]-getsum(pos,0);				//②④两种情况就用总数来减 
			int more_1=cnt[1]-getsum(pos,1);
			if(cmd)tp+=less_0+more_1;						//①②两种情况 
			else tp+=less_1+more_0;							//③④两种情况 
			if(cmd)tp++;									//sum[i]-sum[0]这段
			
			Add(pos,1,cmd);
		}
		if(tp&1)res|=(1<<k);
	}
	return res;
}
int main()
{
	
	
	
	while(cin>>N)
	{
		for(int i=1; i<=N; i++)
		{
			scanf("%d",sum+i);
			sum[i]+=sum[i-1];
		}
		cout<<solve()<<endl;
	}
}

这是以前的:

#include"bits/stdc++.h"
using namespace std;
typedef long long LL;
const int maxn=1e6+5;
const int MOD=998244353;
int xo[maxn],a[maxn];
int tree[maxn];
LL sum[maxn],P[maxn];
int N,n;
LL Cnt[25][2];//极限数据1e5再左移20位就爆int了 
LL solve1()
{
    LL res=0;
    memset(Cnt,0,sizeof(Cnt));
    for(int k=0;k<=20;k++)
    {
        for(int r=1;r<=N;r++)
        {
            int t=1&(xo[r]>>k);
            if(t==1)res+=1<<k;//相当于就他一个数这个区间
            Cnt[k][t]++;
            res+=Cnt[k][t^1]<<k;
            res%=MOD;
        }
    }
    return res;
}
void Add(int pos)
{
	if(pos<0)return ;//有-1,所以要提前退出 
	pos++;//因为有0这个位置,所以树状数组的都加1
	for(int i=pos; i<maxn; i+=i&-i)tree[i]++;;
}
int getsum(int pos)
{
	pos++;
	int res=0;
	for(int i=pos; i>0; i-=i&-i)res+=tree[i];
	return res;
}
int idx(LL x)//找最后一个小于等于他的数,所以是upper_bound-1
{
	return upper_bound(P,P+n+1,x)-P-1;//注意,还有减1
}
LL solve2()
{
	LL res=0;
	for(int k=0; k<=40; k++)//差不多1e11,所以40位就差不多了 
	{
		memset(tree,0,sizeof tree);
		LL tp=0;
		for(int i=1;i<=N;i++)P[i]=sum[i]%(1LL<<(1+k));
		sort(P,P+1+N);
		//unique这里弄出来是有n+1个数,但是我想把P[n]作为最后一个数,因此,还减了一个1
		n=unique(P,P+1+N)-P-1;
		for(int i=0;i<=N;i++)//这里要从0开始,我也不是很懂为什么
		{
			LL now=sum[i]%(1LL<<(k+1));
			Add(idx(now));
			tp+=getsum(idx(now-(1LL<<k)));//第一个不等式
			tp+=getsum(idx(now+(1LL<<k)))-getsum(idx(now));//第二个不等式,求夹在中间的那一坨
		}
		if(tp&1LL)res|=(1LL<<k);
	}
	return res;
}
int main()
{
    while(cin>>N)
    {
        for(int i=1;i<=N;i++)
        {
            scanf("%d",a+i);
            sum[i]=sum[i-1]+a[i];
            xo[i]=xo[i-1]^a[i];
        }
        cout<<solve1()<<" "<<solve2()<<endl;
    }
}```

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章