線段樹或樹狀陣列求逆序數(附例題)
線段樹或樹狀陣列求逆序數
假設給你一個序列 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——2299(Ultra-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],這兩個就算一對逆序對。其實也就是對於每個數而言,找找排在其前面有多少個比自己大的數。那麼思路