線段樹或樹狀數組求逆序數(附例題)

來源:互聯網
上載者:User

標籤:線段樹   樹狀數組   資料結構   

學習了博主:MyZee    , shengweison 的文章

                       線段樹或樹狀數組求逆序數


假設給你一個序列 6 1 2 7 3 4 8 5,  首先我們先手算逆序數, 設逆序數為 N;

6的前面沒有比他大的數 N +=0

1的前面有一個比他大的數 N+=1

2的前面有一個比他大的數 N+=1

7的前面沒有比他大的數 N+=0

... 最後得到 N = 0 + 1 + 1 + 0 + 2 + 2 + 0 + 3 = 9


其實我們可用用線段樹,或者樹狀數組類比這個過程。 又因為線段樹和樹狀數組的效率較高,所以可行


假設我們將 序列 6 1 2 7 3 4 8 5 存入數組num【】 中, num【1】=6 , num【2】=1...

那麼每次,我們可以將 num【i】 插入到 線段樹或者樹狀數組中,並賦值為 1,

 我們求和sum,sum等於線段樹中 1 到 num【i】的和 , 那麼這個 sum 表示的值就是當前比num【i】小的數量;

而當前一共有 i 個數 , 所以 當前 比num【i】大的數量就是 i - sum;

所以 我們統計所有的 i - sum , 它們的和就是逆序數。 類比了上面手算的過程。


【線段樹的關鍵代碼】

<pre name="code" class="cpp">int count=0;for(int i=1;i<=n;i++){       Insert(1,num[i],num[i],1); //插入 num[i],並賦值1       count+=(i-(Query(1,1,num[i])));}

【樹狀數組的關鍵代碼】

long long  ans=0;for(int i=1;i<=n;i++){add(N[i].id);ans+=(i-Sum(N[i].id));}

當然,這裡查詢的數 的 id 都是預設從 1 到 N 的,如果 題目要求輸入的數超過這個範圍,就需要用到離散化,

這個在後面的題目會介紹到。


【這裡先給一個求1~n的逆序數】

//線段樹 求逆序數#include <iostream>#include <cstdio>#include <cstring>#define L(a) a<<1#define R(a) (a<<1)|1const int maxn = 51000;int ans[maxn];struct node{int num,l,r;}tree[maxn<<2];int n;void Build(int m,int l, int r){tree[m].l=l;tree[m].r=r;if(tree[m].l==tree[m].r){tree[m].num=0;return ;}int mid = (tree[m].l+tree[m].r)>>1;Build(L(m),l,mid);Build(R(m),mid+1,r); //並不要回溯, 建立空樹}void Insert(int m,int l,int r,int x){if(tree[m].l==l&&tree[m].r==r){tree[m].num+=x; return ;}int mid = (tree[m].l+tree[m].r)>>1;if(r<=mid)Insert(L(m),l,r,x);else if(l>mid)Insert(R(m),l,r,x);else{Insert(L(m),l,mid,x);Insert(R(m),mid+1,r,x);}tree[m].num=tree[L(m)].num+tree[R(m)].num;}int Query(int m,int l,int r){if(tree[m].l==l&&tree[m].r==r)return tree[m].num;int mid = (tree[m].l+tree[m].r)>>1;if(r<=mid)return Query(L(m),l,r);if(l>mid)return Query(R(m),l,r);return Query(L(m),l,mid)+Query(R(m),mid+1,r);}int main(){int a,n,i,t;scanf("%d",&t);while(t--){int k=0;scanf("%d",&n);memset(tree,0,sizeof(tree));Build(1,1,n);for(int i=1;i<=n;i++){scanf("%d",&ans[i]);}for(int i=1;i<=n;i++){Insert(1,ans[i],ans[i],1);// 每個位置插入1k+=(i - Query(1,1,ans[i]));}printf("%d\n",k);}return 0;}

HDU 1394求多個逆序數中的最小值

#include <iostream>#include <cstdio>#include <cstring>#define L(a) a<<1#define R(a) a<<1|1using namespace std;int n;const int maxn = 5005;int num[maxn];struct node{    int l,r,sum;}tree[maxn<<2];void Build(int m,int l,int r){    tree[m].l=l; tree[m].r=r;    if(tree[m].l==tree[m].r){ //如果當前節點的左右節點相同,即葉子節點        tree[m].sum=0;        return ;    }    int mid = (tree[m].l+tree[m].r)>>1;    Build(L(m),l,mid);    Build(R(m),mid+1,r); }void Insert(int m,int l,int r,int x){    if(tree[m].l==l&&tree[m].r==r){        tree[m].sum+=x;        return ;    }    int mid = (tree[m].l+tree[m].r)>>1;    if(mid>=r) //這裡是大於等於        Insert(L(m),l,r,x);    else if(mid<l)        Insert(R(m),l,r,x);    else{        Insert(L(m),l,mid,x);        Insert(R(m),mid+1,r,x);    }    tree[m].sum=tree[L(m)].sum+tree[R(m)].sum;}int Query(int m,int l,int r){    if(tree[m].l==l&&tree[m].r==r){        return tree[m].sum;    }    int mid = (tree[m].l+tree[m].r)>>1;    //這裡和 Insert 一樣    if(mid>=r)        return Query(L(m),l,r);    if(mid<l)        return Query(R(m),l,r);    return Query(L(m),l,mid)+Query(R(m),mid+1,r);}int main(){        while(scanf("%d",&n)!=EOF){        memset(tree,0,sizeof(tree));        Build(1,1,n);        for(int i=1;i<=n;i++){            scanf("%d",&num[i]);            num[i]++;        }        int count=0;        for(int i=1;i<=n;i++){            Insert(1,num[i],num[i],1);            count+=(i-(Query(1,1,num[i])));        }        int ans = count;        for(int i=1;i<=n;i++){            num[i]--;            count = count - num[i]*2 + n -1;            if(count<ans)                ans = count;        }         printf("%d\n",ans);    }    return 0;}

NYOJ 117 求逆序數

這個需要用到離散化,

建立一個結構體包含val和id, val就是輸入的數,id表示輸入的順序。然後按照val從小到大排序,如果val相等,那麼就按照id排序。

如果沒有逆序的話,肯定id是跟i(表示拍好後的順序)一直一樣的,如果有逆序數,那麼有的i和id是不一樣的。所以,利用樹狀數組的特性,我們可以簡單的算出逆序數的個數。

如果還是不明白的話舉個例子。(輸入4個數)

輸入:9 -1 18 5

輸出 3.

輸入之後對應的結構體就會變成這樣

val:9 -1 18 5

id:  1  2  3  4

排好序之後就變成了

val :  -1 5 9 18

id:      2 4  1  3

2 4 1 3 的逆序數 也是3

之後再利用樹狀數組的特性就可以解決問題了;


因為數字可能有重複, 所以添加操作不再單純的置為1 ,而是 ++;

【原始碼】

 #include <iostream>#include <cstdio>#include <algorithm>#include <cstring>using namespace std;int n;const int maxn = 1000005;struct node{int val,id;}N[maxn];int c[maxn];int cmp(const node &a,const node& b ){if(a.val==b.val)return a.id<b.id;return a.val<b.val;}int lowbit(int x){return x&(-x);}void add(int x){while(x<=n){c[x]++; //可能有重複,因為用++ 不用 = 1;x+=lowbit(x);}return;}int Sum(int x){int ans = 0;while(x>0){ans+=c[x];x-=lowbit(x);}return ans;}int main(){int T;scanf("%d",&T);while(T--){scanf("%d",&n);for(int i=1;i<=n;i++){scanf("%d",&N[i].val);N[i].id=i;}sort(N+1,N+n+1,cmp);//從 1 - n 排序 memset(c,0,sizeof(c)); //不要忘記初始化long long  ans=0; //用 int  會爆掉for(int i=1;i<=n;i++){add(N[i].id);ans+=(i-Sum(N[i].id));}printf("%lld\n",ans);}return 0;}        







著作權聲明:本文為博主原創文章,未經博主允許不得轉載。

線段樹或樹狀數組求逆序數(附例題)

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.