Mishka and Interesting sum 區間異或+樹狀陣列+離線處理
阿新 • • 發佈:2019-02-06
知識:樹狀陣列
題意:
給你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,所以必須要進行離線處理。
按照對於單個區間的思路,我們發現這個過程對於這次查詢區間後面(這個後面指的是按照右邊界排序時的後面
程式碼:
#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();
}
執行結果的話,感覺算的還是很慢,不知道是資料的原因還是我演算法的問題