1. 程式人生 > >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(); }

執行結果的話,感覺算的還是很慢,不知道是資料的原因還是我演算法的問題

這裡寫圖片描述