1. 程式人生 > >線段樹或樹狀陣列求逆序數(附例題)

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

線段樹或樹狀陣列求逆序數

假設給你一個序列 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)|1
const 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);// 每個位置插入1
			k+=(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|1
using 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;
}        



相關推薦

線段陣列序數例題

線段樹或樹狀陣列求逆序數 假設給你一個序列 6 1 2 7 3 4 8 5,  首先我們先手算逆序數, 設逆序數為 N; 6的前面沒有比他大的數 N +=0 1的前面有一個比他大的數 N+=1 2

POJ——2299Ultra-QuickSort陣列序數

In this problem, you have to analyze a particular sorting algorithm. The algorithm processes a sequence of n distinct integers by swapping two adjacen

咳咳,用陣列序對及例題

關於樹狀陣列,相信大家都已經比較熟悉了。。。 那麼,我們就先來砍一刀例題(嘻嘻) 輸入 給出n以及n個數,求這其中的逆序對個數 PS:逆序對,就是序列中ai>aj且i<j的有序對。 輸入: 6 5 4 2 6 3 1 輸出: 11 n<=5*10^5 ai&

HDU 4911 (陣列序數+離散化)

題目連結:http://acm.hdu.edu.cn/showproblem.php?pid=4911 題意:最多可以交換K次,就最小逆序對數。 思路:逆序數定理,當逆序對數大於0時,若ai<ai+1,那麼交換後逆序對數+1,反之-1。所以只需要求一下逆序數的個數就行了。逆序數的求

POJ 3067 Japan 陣列序數

可以發現規律,用樹狀陣列,求逆序數之和,累加sum(MAXN)-sum(nodes[i].x); Description Japan plans to welcome the ACM ICPC World Finals and a lot of roads must be built for

UVALive 4329 Ping pong陣列序數+順序數

題意:對於給定的一個長度為n序列a,對於每個位置i,若左邊存在一個數a[l],右邊存在一個數a[r],滿足a[l] < a[i] < a[r]或a[l] > a[i] > a[r],則(l, r)構成一對,求總共有多少對。 思路:雖

離散化+陣列序數

離散化是一種常用的技巧,有時資料範圍太大,可以用來放縮到我們能處理的範圍 因為其中需排序的數的範圍0--- 999999999;顯然陣列不肯能這麼大;而N的最大範圍是500000;故給出的數一定可

hdu 2492 Ping pong 陣列 序數

題意:有t組資料,每行資料的第一個數 n 表示有n個人,每個位置上的資料代表選手的技能值,現在要三個人組隊,按照位置的順序,三個人中間的人是裁判,兩邊的選手,裁判的技能值要跟位置一樣位於兩位選手之間。 思路:一個點一個點的判斷,求該點點左邊比它大的數的個數,右邊比它小的數

資料結構實驗之排序五:歸併序數SDUT 3402

歸併排序詳解(戳我)。 以下是搬了別人的。 #include<stdio.h> #include<stdlib.h> long long sum = 0; int a[100005]; int temp[100005]; void Merge(int s1

歸併排序序數排序演算法

歸併排序:歸併排序是建立在歸併操作上的一種有效的排序演算法,該演算法是採用分治法(Divide and Conquer)的一個非常典型的應用。將已有序的子序列合併,得到完全有序的序列;即先使每個子序列有序,再使子序列段間有序。若將兩個有序表合併成一個有序表,稱為

用歸併排序陣列序對數量 poj2299

題目連結:https://vjudge.net/problem/POJ-2299 推薦講解樹狀陣列的部落格:https://blog.csdn.net/int64ago/article/details/7429868 題目意思就是讓我們把無序的一些數字經過相鄰數字間兩兩交換,最後變成不遞減的數字。我們要求

[zoj4046][陣列序(強化版)]

http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=4046 題意:有一個含有n個元素的數列p,每個元素均不同且為1~n中的一個,求出將數列變為迴圈遞增序列至少需要左右相鄰的數交換多少次 題目分析:先看簡化版的題目:如果只有1 2 3

離散化及陣列序對

用樹狀陣列求逆序對的時候注意:要統計b[i]-1的字首和,因為可能有相同值的元素 不去重離散化: sort(a+1, a+n+1, cmp); for(int i=1; i<=n; i++) { if(i == 1 || a[i].val != a[i-1].val) { to

陣列序對【TSOJ 1232】

傳送門就不傳送了,你們也傳送不進來; 就是一個求逆序對的裸題; 直接離散化+樹狀陣列就完事了。 樹狀陣列區間更新和區間查詢寫 這題總是不對; 我也不知道為啥,過幾天再研究吧。 單點更新的樹狀陣列寫對了; 總之就是如果用樹狀陣列的話一定要離散化。 下面是程式碼: #in

tokitsukaze and Inverse Number陣列序對及序對性質

題目連結: tokitsukaze and Inverse Number   題意: tokitsukaze給你一個長度為n的序列,這個序列是1到n的一種排列。 然後她會進行q次操作。每次操作會給你L R k這三個數,表示區間[L,R]往右移動k次。 移動一次的定義是:

陣列序對

第一篇部落格就是樹狀陣列,已經過去半年了我樹狀陣列還是隻會個模板= = CF1042D的題解一直看不懂,看到下面有人說和逆序對有關係,所以還是先補一下逆序對吧。 洛谷P1908是逆序對的模板題,資料很強,很好,就是題解質量參差不齊,很多人在資料加強後根本都是WA的,所以果

poj 3067(陣列序模板

Description Japan plans to welcome the ACM ICPC World Finals and a lot of roads must be built for the venue. Japan is tall island with N

陣列序對 - 手套

手套 描述 你現在有N對手套,但是你不小心把它們弄亂了,需要把它們整理一下。N對手套被一字排開,每隻手套都有一個顏色,被記為0~N-1,你打算通過交換把每對手套都排在一起。由於手套比較多,你每次只能交換相鄰兩個手套。請你計算最少要交換幾次才能把手套排整齊。 輸入 輸入第一

計蒜客 Prefix Free Code 字典 + 陣列 +

1.題意:給你n個字串,給你一個k,意思是你能任選k個字串組成一個長字串。再給你一個長字串問你這個字串在所有任選k個字串組合中字典序排第幾。(所有字串長度之和不大於1e6 , 要求結果對1e9 + 7取模) 2.思路:有康拓展開的思路聯想到:我們如果把所有字串由小到大排序後

陣列 序對】排序

首先需要了解逆序對是什麼:逆序對就是如果i > j && a[i] < a[j],這兩個就算一對逆序對。其實也就是對於每個數而言,找找排在其前面有多少個比自己大的數。那麼思路