1. 程式人生 > >TOJ4114(活用樹狀數組)

TOJ4114(活用樹狀數組)

namespace return 統計 關系 max 按位計算 處理 們的 保存

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(活用樹狀數組)