POJ 2299-Ultra-QuickSort(樹狀陣列求逆序數)
Ultra-QuickSort
Time Limit: 7000MS | Memory Limit: 65536K | |
Total Submissions: 52967 | Accepted: 19434 |
Description
Ultra-QuickSort produces the output
Your task is to determine how many swap operations Ultra-QuickSort needs to perform in order to sort a given input sequence.
Input
Output
Sample Input
5 9 1 0 5 4 3 1 2 3 0
Sample Output
6 0
Source
題目意思:
有一組數,求升序排列需要交換多少次,即對給定的每組數逆序數。可以用選擇排序、歸併排序和樹狀陣列的思想來考慮,但是選擇排序會超時。
解題思路:
這裡我們考慮用樹狀陣列來解決。分兩步,離散化和求逆序數。
①離散化
因為題目中給出的n < 500,000而0 ≤ a[i] ≤ 999,999,999,所以我們可以把輸入的N個數a[i],按大小順序分別對映到1~N。
例如 9 1 0 5 4 可以離散化對映為 5 2 1 4 3.
②求逆序數
“逆序數就是數中各位在它前面有多少個數比它大,求出這些元素個數之和。”
每輸入一個數就更新一次c陣列再判斷一次當前比這個數大的數的個數。
說明:
i是當前已經插入的數字的個數;
num[i]是原序列中的數離散化後的各個數;
getsum(num[i])表示比num[i]小的數的個數,getsum(num[i])等於num[num[i]–lowbit(num[i])+1]+...+num[num[i]];
i-getsum(num[i])表示比num[i]大的數的個數,這就是逆序數。
Note:困擾了我好幾個小時的就是為什麼“getsum(num[i])表示比num[i]小的數的個數”?
想了很久,我的理解是這樣的:
因為是依次插入,每次都做查詢,所以肯定是與當前的數有關。c陣列是對陣列的一種求和統計,每次輸入後需要更新,更新時把該數被包含在c陣列裡的資料全部加一,所以c[i]表示當前比i小的數的個數。
程式碼一:先更新再求和
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define MAXN 500005
int c[MAXN],n,num[MAXN];
struct Node
{
int val,no;
} data[MAXN];
bool cmp(Node a,Node b)
{
return a.val<b.val;
}
int lowbit(int x)
{
return x&(-x);
}
void update(int x,int v)
{
while(x<=n)
{
c[x]+=v;
x+=lowbit(x);
}
}
int getsum(int x)
{
int sum=0;
while(x)
{
sum+=c[x];
x-=lowbit(x);
}
return sum;
}
int main()
{
int i;
long long ans;
while(scanf("%d",&n),n)
{
memset(c,0,sizeof(c));
for(i=1; i<=n; i++)
{
scanf("%d",&data[i].val);
data[i].no=i;//儲存每個數輸入時的下標
}
sort(data+1,data+n+1,cmp);//對輸入的序列排序
for(i=1; i<=n; i++)
{
//離散化,把n個點按大小對映到1~n
//data[i].no是數在原序列中的下標
num[data[i].no]=i;//離散下標表示
}
ans=0;
for(i=1; i<=n; i++)
{
//n是總數,num[i]是原序列中的數離散化後的各個數
//getsum(num[i])表示比num[i]小的數的個數
//getsum(num[i])等於num[num[i]–lowbit(num[i])+1]+...+num[num[i]]
update(num[i],1);
ans+=i-getsum(num[i]);
}
cout<<ans<<endl;
}
}
程式碼二:先求和再更新
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define MAXN 500005
int c[MAXN],n,num[MAXN];
struct Node
{
int val,no;
} data[MAXN];
bool cmp(Node a,Node b)
{
return a.val<b.val;
}
int lowbit(int x)
{
return x&(-x);
}
void update(int x,int v)
{
while(x<=n)
{
c[x]+=v;
x+=lowbit(x);
}
}
int getsum(int x)
{
int sum=0;
while(x)
{
sum+=c[x];
x-=lowbit(x);
}
return sum;
}
int main()
{
int i;
long long ans;
while(scanf("%d",&n),n)
{
memset(c,0,sizeof(c));
for(i=0; i<n; i++)
{
scanf("%d",&data[i].val);
data[i].no=i;//儲存每個數輸入時的下標
}
sort(data,data+n,cmp);//對輸入的序列排序
for(i=0; i<n; i++)
{
//離散化,把n個點按大小對映到1~n
//data[i].no是數在原序列中的下標
num[data[i].no]=i+1;//離散下標表示
}
ans=0;
for(i=0; i<n; i++)
{
//n是總數,num[i]是原序列中的數離散化後的各個數
//getsum(n)是數在原序列中的下標
//getsum(num[i])表示比num[i]小的數的個數
//getsum(num[i])等於num[num[i]–lowbit(num[i])+1]+...+num[num[i]]
ans+=(getsum(n)-getsum(num[i]));
update(num[i],1);
}
cout<<ans<<endl;
}
}
轉載:
樹狀陣列,具體的說是 離散化+樹狀陣列。這也是學習樹狀陣列的第一題.
演算法的大體流程就是:
1.先對輸入的陣列離散化,使得各個元素比較接近,而不是離散的,
2.接著,運用樹狀陣列的標準操作來累計陣列的逆序數。
演算法詳細解釋:
1.解釋為什麼要有離散的這麼一個過程?
剛開始以為999.999.999這麼一個數字,對於int儲存型別來說是足夠了。
還有隻有500000個數字,何必要離散化呢?
剛開始一直想不通,後來明白了,後面在運用樹狀陣列操作的時候,
用到的樹狀陣列C[i]是建立在一個有點像位儲存的陣列的基礎之上的,
不是單純的建立在輸入陣列之上。
比如輸入一個9 1 0 5 4,那麼C[i]樹狀陣列的建立是在,
下標 0 1 2 3 4 5 6 7 8 9
陣列 1 1 0 0 1 1 0 0 0 1
現在由於999999999這個數字相對於500000這個數字來說是很大的,
所以如果用陣列位儲存的話,那麼需要999999999的空間來儲存輸入的資料。
這樣是很浪費空間的,題目也是不允許的,所以這裡想通過離散化操作,
使得離散化的結果可以更加的密集。
2. 怎麼對這個輸入的陣列進行離散操作?
離散化是一種常用的技巧,有時資料範圍太大,可以用來放縮到我們能處理的範圍;
因為其中需排序的數的範圍0---999 999 999;顯然陣列不肯能這麼大;
而N的最大範圍是500 000;故給出的數一定可以與1.。。。N建立一個一一對映;
①當然用map可以建立,效率可能低點;
②這裡用一個結構體
struct Node
{
int v,ord;
}p[510000];和一個陣列a[510000];
其中v就是原輸入的值,ord是下標;然後對結構體按v從小到大排序;
此時,v和結構體的下標就是一個一一對應關係,而且滿足原來的大小關係;
for(i=1;i<=N;i++) a[p[i].ord]=i;
然後a陣列就儲存了原來所有的大小資訊;
比如 9 1 0 5 4 ------- 離散後aa陣列就是 5 2 1 4 3;
具體的過程可以自己用筆寫寫就好了。
3. 離散之後,怎麼使用離散後的結果陣列來進行樹狀陣列操作,計算出逆序數?
如果資料不是很大, 可以一個個插入到樹狀陣列中,
每插入一個數, 統計比他小的數的個數,
對應的逆序為 i- getsum( aa[i] ),
其中 i 為當前已經插入的數的個數,
getsum( aa[i] )為比 aa[i] 小的數的個數,
i- sum( aa[i] ) 即比 aa[i] 大的個數, 即逆序的個數
但如果資料比較大,就必須採用離散化方法
假設輸入的陣列是9 1 0 5 4, 離散後的結果aa[] = {5,2,1,4,3};
在離散結果中間結果的基礎上,那麼其計算逆序數的過程是這麼一個過程。
1,輸入5, 呼叫upDate(5, 1),把第5位設定為1
1 2 3 4 5
0 0 0 0 1
計算1-5上比5小的數字存在麼? 這裡用到了樹狀陣列的getSum(5) = 1操作,
現在用輸入的下標1 - getSum(5) = 0 就可以得到對於5的逆序數為0。
2. 輸入2, 呼叫upDate(2, 1),把第2位設定為1
1 2 3 4 5
0 1 0 0 1
計算1-2上比2小的數字存在麼? 這裡用到了樹狀陣列的getSum(2) = 1操作,
現在用輸入的下標2 - getSum(2) = 1 就可以得到對於2的逆序數為1。
3. 輸入1, 呼叫upDate(1, 1),把第1位設定為1
1 2 3 4 5
1 1 0 0 1
計算1-1上比1小的數字存在麼? 這裡用到了樹狀陣列的getSum(1) = 1操作,
現在用輸入的下標 3 - getSum(1) = 2 就可以得到對於1的逆序數為2。
4. 輸入4, 呼叫upDate(4, 1),把第5位設定為1
1 2 3 4 5
1 1 0 1 1
計算1-4上比4小的數字存在麼? 這裡用到了樹狀陣列的getSum(4) = 3操作,
現在用輸入的下標4 - getSum(4) = 1 就可以得到對於4的逆序數為1。
5. 輸入3, 呼叫upDate(3, 1),把第3位設定為1
1 2 3 4 5
1 1 1 1 1
計算1-3上比3小的數字存在麼? 這裡用到了樹狀陣列的getSum(3) = 3操作,
現在用輸入的下標5 - getSum(3) = 2 就可以得到對於3的逆序數為2。
6. 0+1+2+1+2 = 6 這就是最後的逆序數
分析一下時間複雜度,首先用到快速排序,時間複雜度為O(NlogN),
後面是迴圈插入每一個數字,每次插入一個數字,分別呼叫一次upData()和getSum()
外迴圈N, upData()和getSum()時間O(logN) => 時間複雜度還是O(NlogN).
最後總的還是O(NlogN).
相關文章
- POJ 3067-Japan(樹狀陣列-逆序數)陣列
- HDU2689 Sort it (樹狀陣列求逆序數)陣列
- HDU 1394 Minimum Inversion Number (樹狀陣列求逆序數)陣列
- HDU 2689 Sort it【樹狀陣列求逆序對】陣列
- 樹狀陣列和逆序對陣列
- poj 2481 樹狀陣列陣列
- 【樹狀陣列 求比其小的個數】poj 2353 Stars陣列
- POJ 3928 Ping pong(樹狀陣列)陣列
- POJ-2352 Stars(樹狀陣列)陣列
- 【二維樹狀陣列】poj 2155 Matrix陣列
- 二維樹狀陣列-poj2155陣列
- poj 1195 二維樹狀陣列陣列
- 求區間不同數的個數【樹狀陣列求解】陣列
- POJ 2352-Stars(樹狀陣列-星系)陣列
- POJ 3321-Apple Tree(樹狀陣列)APP陣列
- POJ 2352 Stars(簡單樹狀陣列)陣列
- 二維樹狀陣列--poj1195陣列
- HDU 1541 & POJ 2352 Stars (樹狀陣列)陣列
- POJ 1195 Mobile phones(二維樹狀陣列)陣列
- POJ 3321 Apple Tree(dfs+樹狀陣列)APP陣列
- 樹狀陣列陣列
- POJ 3928-Ping pong(樹狀陣列+加/乘法原理)陣列
- POJ3321 Apple Tree(DFS序 + 樹狀陣列)APP陣列
- 解析樹狀陣列陣列
- LeetCode C++ 劍指 Offer 51. 陣列中的逆序對【歸併排序/樹狀陣列/線段樹】LeetCodeC++陣列排序
- POJ3468 A Simple Problem with Integers---樹狀陣列(區間問題)陣列
- POJ 2352(順路講解一下樹狀陣列)陣列
- D 區間求和 [數學 樹狀陣列]陣列
- POJ 3468 【區間修改+區間查詢 樹狀陣列 | 線段樹 | 分塊】陣列
- hdu4417 樹狀陣列(求指定區間比指定數小的數的個數)陣列
- 陣列元素逆序陣列
- 樹狀陣列詳解陣列
- 樹狀陣列基礎陣列
- hdu 3874 樹狀陣列陣列
- 二維樹狀陣列陣列
- SPOJ DQUERY (離線數狀陣列||線上主席樹)陣列
- 樹狀陣列模板題 & (樹狀陣列 1:單點修改,區間查詢)陣列
- hdu 5147 樹狀陣列陣列