Mishka and Interesting sum 区间异或+树状数组+离线处理

原题Mishka and Interesting sum

知识树状数组

题意

给你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)(区间所有出现过的数异或)

这个很好懂,难点是怎么处理,对于单个区间,我们的办法如下:

  1. 用前缀异或来解决区间的所有数的异或
  2. 对于出现过的数,我们用树状数组来维护,先初始化树状数组(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();
}

运行结果的话,感觉算的还是很慢,不知道是数据的原因还是我算法的问题

这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值