知识:树状数组
题意:
给你n个数,然后询问q次,每次询问查询区间[l,r]里的出现过偶数次的那些数的亦或值
解析:
首先是出现偶数次,到底是哪些数。
设Al为整个区间的异或,Odd为区间内奇数次的数的异或,Even为区间内出现偶数次的数的异或(Even为答案,eg:2,2,3,3,则Even为2^3),我们知道,Al == Odd ^ Even ^Even
,两边^Even -> Al ^ Even == Odd ^ Even
,两边^Odd -> Al ^ Even ^ Odd == Even
,而Even和Odd的并集不就是所以出现过的数吗?所以 answer == Even(出现偶数次的数异或) == Al (区间所有的数异或)^ (Even ^ Odd)(区间所有出现过的数异或)
这个很好懂,难点是怎么处理,对于单个区间,我们的办法如下:
- 用前缀异或来解决区间的所有数的异或
- 对于出现过的数,我们用树状数组来维护,先初始化树状数组(tr[i]表示从i开始前面lowbit(i)个数的异或),我们从第一个数开始扫,每扫到一个数,都判断一下这个数在前面是否出现过,如果出现过,就把前面出现过的位置消掉(add( 前面出现的位置,这个数),add是树状数组的更新,加法改成了异或,前面异或过的位置再一次异或就相当于消掉了),扫到了这个区间的结尾,就把前缀异或 ^ 树状数组查询区间异或,就是ans了。
那么对于多个区间呢?一个一个来会T,所以必须要进行离线处理。
按照对于单个区间的思路,我们发现这个过程对于这次查询区间后面(这个后面指的是按照右边界排序时的后面)的区间不会有影响,所以我们按照右边界升序排序,然后按照这个顺序在N的时间内(只需要扫一次)完成所有区间的求解。
代码:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<string>
#include<cstring>
#include<algorithm>
#include<set>
#include<map>
#include<list>
#include<vector>
#include<stack>
#include<queue>
#include<ctime>
#include<cstdlib>
//#include<windows.h>
#include<functional>
#define D long long
#define F double
#define MAX 0x7fffffff
#define MIN -0x7fffffff
#define mmm(a,b) memset(a,b,sizeof(a))
#define pb push_back
#define mk make_pair
#define fi first
#define se second
#define pill pair<int, int>
#define for1(i,a,b) for(int i=a;i<=b;i++)
#define for2(i,a,b) for(int i=a;i>=b;i--)
#define ini(n) scanf("%d",&n)
#define outisp(n) printf("%d ",n)
#define outiel(n) printf("%d\n",n)
using namespace std;
#define N 1000100
#define MOD ((int)1e9+7)
#define random(a,b) (rand()%(b-a+1)+a)
#define stop Sleep(2000)
#define CLS system("cls")
const string el="\n";
const string elel="\n\n";
const string sp=" ";
const string spsp=" ";
const string tab="\t";
int n,m;
int nu[N];
int tr[N];//树状数组
map<int,int>M;//M[5]=3表示5最后一次出现在3位置(最后一次是指到现在for语句进行的位置为止,每次出现都会及时更新)
map<int,int>pre;//pre[4]==2表示第四个位置前一个和第四个位置上的数相同的数的位置是2
//M是用来更新pre的,起作用的是pre
int presum[N];//同前缀和
int ans[N];
struct node{
int l,r,ID;
bool operator < (const node & x) const{//按照区域右界从小到大排序
return r<x.r;
}
}e[N];
int lowbit(int i){
return i&(-i);
}
void add(int pos,int val){
for(int i=pos;i<=n;i+=lowbit(i)){
tr[i]^=val;
}
}
int query(int r){
int re=0;
while(r>0){
re^=tr[r];
r-=lowbit(r);
}
return re;
}
void INNUM(){
ini(n);for1(i,1,n){
ini(nu[i]);
presum[i]=presum[i-1]^nu[i];
if(!M[nu[i]])M[nu[i]]=i;//如果是第一次出现,就存下现在的位置
else pre[i]=M[nu[i]],M[nu[i]]=i;//如果第i个数的前面出现过这个数,就用pre存下前一次这个数出现的位置,同时更新 M
}
}
void INQUERY(){
ini(m);for1(i,1,m){
ini(e[i].l);ini(e[i].r);e[i].ID=i;//保存下位置,在sort后就可以知道原来是第几个了
}
sort(e+1,e+1+m);
}
//之前以为离线处理是求出一个区间,再用这个区间的答案来推其他区间,看懂了才知道原来就这么个事
//就是按照数从小到大走一遍,走到了一个区间的结尾,就给这个询问的答案赋值即可
//我们可以做一遍一个区间的情况,也是按照数从小到大走一遍,走到这个区间的右边界
//在这个过程中,每遇到一个重复的,就去掉前面的那个
//现在是多个区间一起处理
//每次去掉前面那个的时候,对于现在和后面(按照右边界来说的后面)的区间的求值来说不会出错
//只会影响前面的,所以我们按照右边界排序定下求值的先后
void PROCESS(){
int ar=1;//当前进行到的询问区间
int i=1;//当前进行到的数字
for(;ar<=m;ar++){
for(;i<=e[ar].r;i++){
if(pre[i])add(pre[i],nu[i]);//如果前面有相同的,消掉前面那个
add(i,nu[i]);//在走一遍的过程中完成对树状数组的初始化,其实初始化可以放到外面去
}
//走完了这个区间的右边界,就可以得出这次询问的ans了
int ll=e[ar].l,rr=e[ar].r;
ans[e[ar].ID]=presum[rr]^presum[ll-1]^query(rr)^query(ll-1);
}
}
void OUTANS(){
for1(i,1,m){
outiel(ans[i]);
}
}
int main(){
INNUM();
INQUERY();
PROCESS();
OUTANS();
}
运行结果的话,感觉算的还是很慢,不知道是数据的原因还是我算法的问题