標籤:線段樹 樹狀數組 資料結構
學習了博主: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;}
著作權聲明:本文為博主原創文章,未經博主允許不得轉載。
線段樹或樹狀數組求逆序數(附例題)