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;
    }
}```

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