文章目錄
題目鏈接:
https://www.lydsy.com/JudgeOnline/problem.php?id=4017
https://acm.njupt.edu.cn/problem/BZOJ4017
第一問:求所有區間異或的和
表示前個數的異或值,表示前個數的和
第一問:用表示第位的個數和的個數,樸素的做法是枚舉和要
我們先枚舉,如果的第位是,要是有個的第位是就好啦~,那我們的這個區間的第位就是了,我們要做的就是統計出每枚舉的一個有多少個與他異或是的,而且不需要知道具體是哪幾個,只需要知道有多少個,那麼個數就是裏面保存的,是就找,是就找。
第二問:求所有區間和的異或
第二問其實就是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;
}
}```