TOJ4114(活用樹狀數組)
TOJ指天津大學onlinejudge
題意:給你由N個數組成的數列,算出它們的所有連續和的異或和,比如:數列{1,2},則answer = 1 ^ 2 ^ (1 + 2) = 0。
這道題有幾個關鍵點:
1.這道題要將十進制數換成二進制數,並且對這些二進制數按位計算,比如說上面的式子,我們將它列成豎式:
01
10
11
可以發現,對於第一位來說,一共有2個1,是偶數,異或值為0。answer += 0 * (1<<0)。對於第二位來說,一共有2個1,異或值為0,answer += 0 * (1<<1)。最終answer = 0;
2.Sum(i~j) = S[j] - S[i - 1]。 (S[0] = 0) 所以我們算式可以看成(S[1] - S[0]) ^ (S[2] - S[1]) ^ (S[2] - S[0]) ^ (S[3] - S[0]) ^ (S[3] - S[1]) ^ (S[3] - S[2]) ^ ..........
所以我們可以按塊來看這些算式中的項,每一塊就S[i]與S[i - 1]......S[0]做差得到的。
綜上,我們計算異或和時,我們只需要預處理一下,算出每一個S[i],然後從頭到尾掃n次,每一次計算出這些二進制數某一位一共有多少個1(就像上面所舉例子一樣),看看是偶數還是奇數,
如果是奇數,就answer += 1 * (1<<i)。
現在來說掃描時候的具體操作,假設我們現在已經計算完了S[1]....S[i]這些組成異或和的各項在某一位k的1的個數,在掃到S[i + 1]時,相當於又多了(S[i +1] - S[0]) ^ .......^ (S[i + 1] - S[i])這 i 項來統計,
我就只需要計算出S[i + 1]與前面各項S[ j ] (j屬於[ 0 , i ]) 的差在這一位產生了多少個1,對於S[i + 1]與某一個S[ j ]來說,這取決於S[i + 1]和S[ j ]在這一位的值和S[i + 1]與S[ j ]在更低諸位上的大小關系。
在這裏,我將S[i + 1]在這一位的值記為A1,更低的諸位統一記為B1,S[ j ]在這一位的值記為A2,更低諸位的統一記為B2。
1.當A1 == 1時,有兩種情況可以產出一個1:
情況Ⅰ:A2 == 0,且B1 >= B2。
情況Ⅱ:A2 == 1,且B1 < B2(可以從前面借位來產生1,就像10 - 9 = 1這樣)
2.當A1 == 0時,有兩種情況可以產出一個1:
情況Ⅰ:A2 == 0,且B1< B2
情況Ⅱ:A2 == 1,且B1 >= B2。
由此可以看出,對於S[i + 1]與前面諸前綴和之差在某一位能產生多少個1,我只要知道S[ j ]比這一位低的諸位與S[i + 1]比這一位低的諸位之大小關系即可。
這個可以用樹狀數組來保存,樹狀數組的下標表示的是值域,BIT[ Bi ]中存的比這一位低的諸位B小於等於Bi的S[ k ]的個數,由於我們掃這一遍S[ i ]的目的就是為了計算特定的一位,
所以我們有多少位就掃多少次,掃完一次就清空一次樹狀數組。由於還跟S[ j ]在這一位的值有關,所以我們開兩個樹狀數組,一個記錄在這一位為1的S[i],一個則記錄在這一位為0的S[j]。
對於每一個S[i]我們邊算邊存,如果S[i]在這一位為1,則存在BIT_1中;如果為0,則存在BIT_0中。
#include<stdio.h> #include<string.h> #include<algorithm> #include<iostream> #define maxn 100005 #define maxn2 1000005 using namespace std; int BIT_0[maxn2],BIT_1[maxn2]; int S[maxn]; int lowbit(int k) { return (k & (-k)); } void add(int v,int pos,int mmax,int a[]) { while(pos <= mmax){ a[pos] += v; pos += lowbit(pos); } } int sum(int pos,int a[]) { int ret = 0; while(pos > 0){ ret += a[pos]; pos -= lowbit(pos); } return ret; } int main() { int T; scanf("%d",&T); while(T--){ memset(S,0,sizeof(S)); int n; scanf("%d",&n); for(int i = 1;i <= n;++i){ scanf("%d",&S[i]); S[i] += S[i - 1]; } int temp = S[n]; int len = 0; while(temp > 0){ temp = temp>>1; ++len; } int answer = 0; for(int i = 0;i < len;++i){ int cnt = 0; int cnt_0 = 0,cnt_1 = 0,mmax = (1<<i); for(int j = 0;j <= n;++j){ int tail = S[j] % (1<<i); ++tail; int t_cnt = 0; if(S[j] & (1<<i)){ t_cnt += sum(tail,BIT_0); t_cnt += (cnt_1 - sum(tail,BIT_1)); add(1,tail,mmax,BIT_1); ++cnt_1; } else{ t_cnt += sum(tail,BIT_1); t_cnt += (cnt_0 - sum(tail,BIT_0)); add(1,tail,mmax,BIT_0); ++cnt_0; } cnt += t_cnt; } if(cnt & 1) answer += (1<<i); memset(BIT_0,0,sizeof(BIT_0)); memset(BIT_1,0,sizeof(BIT_1)); } printf("%d\n",answer); } return 0; }
這裏有一個小技巧,由於lowbit( )不好處理0,所以我把每一個tail都加1,(tail的取值最小就是0),這樣sum(x)其實算的是tail在[0,x-1]之間的S[j]
本題啟發:樹狀數組的下標用來表示值域
TOJ4114(活用樹狀數組)