文章目录
题目链接:
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;
}
}```