1. 程式人生 > >逆序對(樹狀陣列)題解

逆序對(樹狀陣列)題解

 

逆序對題解出處(1266)

老實說,還沒有歸併排序快

先上程式碼,再解釋

#include<cstdio>
#include<algorithm>
using namespace std;
inline void read(long long &x) {
    x=0;
    long long f=1;
    char s=getchar();
    while(s<'0'||s>'9') {
        if(s=='-')f=-1;
        s=getchar();
    }
    while(s>='0'&&s<='9') {
        x=x*10+s-48;
        s=getchar();
    }
    x*=f;
}
inline void pr(long long x) {
    if(x<0)x=-x;
    if(x>9)pr(x/10);
    putchar(x%10+48);
}
struct node {
    long long num,id;
} a[500005];
long long lsh[500005],c[500005],b[500005],n,k,ans;
inline int lowbit(int x) {
    return x&-x;
}
inline void update(int x,int k) {
    for(int i=x; i<=n; i+=lowbit(i))
        c[i]+=k;
}
inline int sum(int x) {
    int ans=0;
    for(int i=x; i>0; i-=lowbit(i))
        ans+=c[i];
    return ans;
}
inline bool cmp(node a,node b) {
    return a.num<b.num;
}
int main() {
    read(n);
    for(int i=1; i<=n; i++)
        read(a[i].num),a[i].id=i;
    sort(a+1,a+1+n,cmp);
    int cnt=0;
    for(int i=1; i<=n; i++) {
        if(a[i].num!=a[i-1].num)
            cnt++;
        lsh[a[i].id]=cnt;
    }
    for(int i=1; i<=n; i++) {
        update(lsh[i],1);
        ans+=i-sum(lsh[i]);
    }
    pr(ans);
}

現在我們來分段解釋(快讀快輸不解釋)

先來看樹狀陣列是個啥

大概就是這麼個玩意兒,有什麼用後面再解釋

然後來看lowbit:

lowbit:

       lowbit(i)的意思是i 轉化成二進位制數之後,保留最低位的1及其後面的0,截斷前面的內容,然後再轉十進位制數,這個數也是樹狀陣列中i號位的子葉個數。比如lowbit(22)的意思是將 22 轉化成二進位制數之後得到10110,保留末位的1及其後的0,並截斷前面的內容,得到10,轉化為十進位制數為2,即lowbit(22)=2,證明C[22]的子葉數為

2個。

 

1、求lowbit方法一:

       原數為x(十進位制),先將原數轉化成二進位制之後的最後一位1替換成0,然後再用原數減去替換掉最後一位1後的數(十進位制相減),答案就是lowbit(i)的結果;

   lowbit(i)

   {

         return   i - ( i & ( i – 1 ) );

   }

  說明:

i的二進位制可以看做A1B(A是最後一個1之前的部分,B是最後一個1之後的0)

            i-1的二進位制可以看做A0C(C是和B一樣長的1)

                       i & (i - 1)的二進位制就是A1B & A0C = A0B

            i – (i & (i - 1))的二進位制就是A1B – A0B = 0…010…0

2、求lowbit方法二:

       原i(十進位制),先將原數轉化成二進位制之後,在與原數相反數的二進位制按位與,答案就是lowbit(i)的結果;

  lowbit(i)

  {

        return   i & -i;

  }

  例如:lowbit(22)=2

            22的二進位制原碼011010,正數的補碼等於它的原碼011010

            -22的二進位制原碼111010,負數的補碼等於它的原碼取反加1,為100110

            011010  & 100110 = 000010 正數轉換成原碼後依然是000010

            所以lowbit(22)=2

不懂自己百度

根據這個特點

void update(int k,int x)

  {

        for(int i = k; i <= n; i += lowbit(i))

        C[i] += x;

  }

結合圖片,可以求出c陣列的值。

然後來看如何求字首和:

求字首和B[]段程式碼    (PS:此區間為字首和,也就是1~i)   

   int Sum(int k)

   {

    for(int i = k; i > 0; i -= lowbit(i) )

    B[k] += C[i];

    return  B[k]; 

   }

自己結合影象理解

逆序對其實就是求前面有幾個比他大的數,然後把每一個的和累加起來就好了

程式碼分析:

int main() {
    read(n);
    for(int i=1; i<=n; i++)
        read(a[i].num),a[i].id=i;
    sort(a+1,a+1+n,cmp);
    int cnt=0;
    for(int i=1; i<=n; i++) {
        if(a[i].num!=a[i-1].num)//考慮重複的情況,這裡可以用STL+二分離散化
            cnt++;
        lsh[a[i].id]=cnt;//離散化可以使資料很大時將陣列開小
    }
    for(int i=1; i<=n; i++) {
        update(lsh[i],1);
        ans+=i-sum(lsh[i]);
    }
    pr(ans);
}

在最後為什麼要用ans+=i-sum(lsh[i])留給大家自己思考