標題:小朋友排隊
n 個小朋友站成一排。現在要把他們按身高從低到高的順序排列,
但是每次只能交換位置相鄰的兩個小朋友。
每個小朋友都有一個不高興的程度。開始的時候,所有小朋友的不高興程度都是0。
如果某個小朋友第一次被要求交換,則他的不高興程度增加1,如果第二次要求他交換,則他的不高興程度增加2(即不高興程度爲3),依次類推。當要求某個小朋友第k次交換時,他的不高興程度增加k。
請問,要讓所有小朋友按從低到高排隊,他們的不高興程度之和最小是多少。
如果有兩個小朋友身高一樣,則他們誰站在誰前面是沒有關係的。
【數據格式】
輸入的第一行包含一個整數n,表示小朋友的個數。
第二行包含 n 個整數 H1 H2 … Hn,分別表示每個小朋友的身高。
輸出一行,包含一個整數,表示小朋友的不高興程度和的最小值。
例如,輸入:
3
3 2 1
程序應該輸出:
9
【樣例說明】
首先交換身高爲3和2的小朋友,再交換身高爲3和1的小朋友,再交換身高爲2和1的小朋友,每個小朋友的不高興程度都是3,總和爲9。
【數據規模與約定】
對於10%的數據, 1<=n<=10;
對於30%的數據, 1<=n<=1000;
對於50%的數據, 1<=n<=10000;
對於100%的數據,1<=n<=100000,0<=Hi<=1000000。
資源約定:
峯值內存消耗 < 256M
CPU消耗 < 1000ms
歸併排序可以求總的逆序對,這個大家應該是知道的 。
他人 歸併排序參考
此題所求並非是總的逆序對, 而是相對的。
解題思想:
如果有如下序列:
3 6 4 7 5 2 0 1
那麼對於1來說,要想回到前面, 必須要經過 2 5 7 4 6 3 次序並不重要, 重要的是個數。
那麼對於2來說,要被0 1經過。
那麼總結一下就是:
就是相對於 2 來說有幾個逆序對 即: 小於2並且在2右邊的,大於2並且在其左邊的, 取之和即爲所求。
接下來一張草圖,來解釋歸併求逆序對的過程。
歸併排序求逆序對代碼如下:
去掉含有 下面兩個變量的代碼所在行, 就是歸併排序 ( ni_num_sum,ni_num_sum_temp )
#define MAX 1000005
// 原數組
long num[MAX] = {0};
// 輔助排序
long num_sort[MAX] = {0};
// 去掉含有 下面兩個變量的代碼所在行, 就是歸併排序
// 記錄左右兩邊的逆序 個數
long ni_num_sum[MAX] = {0};
// 輔助記錄
long ni_num_sum_temp[MAX] = {0};
void pai_sort(int l, int mid, int r){
int i = l;
int j = mid + 1;
int index = l;
while(i <= mid && j <= r){
if(num[i] < num[j]){ // 後面大於前面
num_sort[index] = num[i];
ni_num_sum[index] = ni_num_sum_temp[i] + (j - mid - 1); // 逆序數
++i;
}else if(num[i] > num[j]){ // 前面 大於 後面 出現逆序
num_sort[index] = num[j];
ni_num_sum[index] = ni_num_sum_temp[j] + (mid - i + 1); // 逆序數
++j;
} else{// 等於, 不需要加逆序數
num_sort[index] = num[i];
++i;
num_sort[index] = num[j];
++j;
}
++index;
}
while(i <= mid){
if(num[i] != num_sort[index]) // 如果出現相等的情況, 就不要計算
ni_num_sum[index] = ni_num_sum_temp[i] + (j - mid - 1);
num_sort[index] = num[i];
++index;
++i;
}
while(j <= r){
if(num[i] != num_sort[index]) // 如果出現相等的情況, 就不要計算
ni_num_sum[index] = ni_num_sum_temp[j] + (mid - i + 1);
num_sort[index] = num[j];
++index;
++j;
}
// 排序完成 放回原數組, 並且將逆序結果緩存等待取用。
for(i = l; i <= r; ++i){
num[i] = num_sort[i];
ni_num_sum_temp[i] = ni_num_sum[i]; // 緩存上一次的逆序對數
}
}
void gui_pai(int l, int r){
if(l < r){
int mid = (l + r) >> 1;
gui_pai(l, mid);
gui_pai(mid + 1, r);
pai_sort(l, mid, r);
}
}
那麼求出逆序對之後列 ?
之後便是轉換成不滿意程度了。
也好解決:
比如 2 有 7 個逆序對,那麼就是 1 + 2 + 3 + 4 + 5 + 6 + 7 也就是n - 1求和:
對於 n 到 1的求和 求和公式爲 **( ( (n + 1) * n) / 2 ) ** 。
代碼如下:
// 1 - n 連續數 求和
long sum_0_to_n(long n){
return ((n + 1) * n )>> 1;
}
完整代碼:
#include<stdio.h>
#define MAX 1000005
// 時間複雜度 O( N*log2(N) )
long N = 0;
// 原數組
long num[MAX] = {0};
// 輔助排序
long num_sort[MAX] = {0};
// 記錄左右兩邊的逆序 個數
long ni_num_sum[MAX] = {0};
// 輔助記錄
long ni_num_sum_temp[MAX] = {0};
unsigned long long ans = 0;
// 1 - n 連續數 求和
long sum_0_to_n(long n){
return ((n + 1) * n )>> 1;
}
void pai_sort(int l, int mid, int r){
int i = l;
int j = mid + 1;
int index = l;
while(i <= mid && j <= r){
if(num[i] < num[j]){ // 後面大於前面
num_sort[index] = num[i];
ni_num_sum[index] = ni_num_sum_temp[i] + (j - mid - 1); // 逆序數
++i;
}else if(num[i] > num[j]){ // 前面 大於 後面 出現逆序
num_sort[index] = num[j];
ni_num_sum[index] = ni_num_sum_temp[j] + (mid - i + 1); // 逆序數
++j;
} else{// 等於, 不需要加逆序數
num_sort[index] = num[i];
++i;
num_sort[index] = num[j];
++j;
}
index++;
}
while(i <= mid){
if(num[i] != num_sort[index])
ni_num_sum[index] = ni_num_sum_temp[i] + (j - mid - 1);
num_sort[index] = num[i];
++index;
++i;
}
while(j <= r){
if(num[i] != num_sort[index])
ni_num_sum[index] = ni_num_sum_temp[j] + (mid - i + 1);
num_sort[index] = num[j];
++index;
++j;
}
// 放回
for(i = l; i <= r; i++){
num[i] = num_sort[i];
ni_num_sum_temp[i] = ni_num_sum[i]; // 緩存上一次的逆序對數
}
}
void gui_pai(int l, int r){
if(l < r){
int mid = (l + r) >> 1;
gui_pai(l, mid);
gui_pai(mid + 1, r);
pai_sort(l, mid, r);
}
}
int main(){
scanf("%d", &N);
for(int i = 0; i < N; i++){
scanf("%d", num + i);
}
gui_pai(0, N - 1);
// 使用逆序對 計算結果
for(int i = 0; i < N; ++i){
ans += sum_0_to_n(ni_num_sum[i]);
}
printf("%lld\n", ans);
for(int i = 0; i < N; i++){
printf("%d ", ni_num_sum[i]);
}
return 0;
}
解題思想二 數狀數組:
同樣是求逆序對, 但是求解方式不同。
同樣一張草圖, 表示解題過程。
#include<stdio.h>
#define N_MAX 100005
// 時間複雜度 O( N*log2(N) )
long N = 0;
// 原數組
long num[N_MAX] = {0};
// 這裏需要使用數字的最大值存放數據, 因爲這相等於揹包
#define MAX 1000005
// 樹狀數組解法
// 需要兩個數組
long tree_num_left[MAX];
long tree_num_right[MAX];
// 存放求和結果
long ans_num_left[MAX];
long ans_num_right[MAX];
// 存放最大方便確定掃描區間 減少複雜度
long max_num = -999999;
inline int low_bit(int t){
return t&(-t);
}
// 求和
inline long tree_sum(long tree_num[], long index){
long sum = 0;
for(long i= index; i > 0; i -=low_bit(i)){
sum += tree_num[i];
}
return sum;
}
// 更新單節點
inline void update(long tree_num[], int index, long num){
for(int i = index; i <= max_num; i += low_bit(i)){
tree_num[i] += num;
}
}
int main(){
scanf("%d", &N);
for(int i = 0; i < N; i++){
scanf("%d", num + i);
if(num[i] > max_num){
max_num = num[i];
}
}
max_num ++; // 因爲會出現0的情況, 將最大值加1就可以, 相當於向右偏移1, 當然 多加也無所謂
// 樹狀數組解法
// 更新 由左向右掃描
for(int i = 0; i < N; ++i){
update(tree_num_left, num[i] + 1, 1);
// 若是 替換成 ans_num_right[num[i]] 數據將變得有序 , 但是後續合併和計算複雜度 最差 將會變高10倍
// 而且並不需要有序, 只需要結果就好
ans_num_left[i] = tree_sum(tree_num_left, max_num) - tree_sum(tree_num_left, num[i] + 1);
}
// 更新 由右向左掃描
for(int i = N - 1; i >= 0; --i){
update(tree_num_right, num[i] + 1, 1);
ans_num_right[i] = tree_sum(tree_num_right, num[i]);
}
// 合併
unsigned long long ans_2 = 0;
for(int i = 0; i < N; ++i){
ans_num_right[i] += ans_num_left[i];
// 求值
ans_2 += sum_0_to_n(ans_num_right[i]);
printf("%ld ", ans_num_right[i]);
}
puts("");
printf("樹狀數組求得:%lld\n", ans_2);
return 0;
}
運行截圖:
歸併排序 和 數狀數組全部代碼
#include<stdio.h>
#define N_MAX 100005
// 時間複雜度 O( N*log2(N) )
long N = 0;
// 原數組
long num[N_MAX] = {0};
// 輔助排序
long num_sort[N_MAX] = {0};
// 記錄左右兩邊的逆序 個數
long ni_num_sum[N_MAX] = {0};
// 輔助記錄
long ni_num_sum_temp[N_MAX] = {0};
unsigned long long ans = 0;
// 1 - n 連續數 求和
long sum_0_to_n(long n){
return ((n + 1) * n )>> 1;
}
void pai_sort(int l, int mid, int r){
int i = l;
int j = mid + 1;
int index = l;
while(i <= mid && j <= r){
if(num[i] < num[j]){ // 後面大於前面
num_sort[index] = num[i];
ni_num_sum[index] = ni_num_sum_temp[i] + (j - mid - 1); // 逆序數
++i;
}else if(num[i] > num[j]){ // 前面 大於 後面 出現逆序
num_sort[index] = num[j];
ni_num_sum[index] = ni_num_sum_temp[j] + (mid - i + 1); // 逆序數
++j;
} else{// 等於, 不需要加逆序數
num_sort[index] = num[i];
++i;
num_sort[index] = num[j];
++j;
}
index++;
}
while(i <= mid){
if(num[i] != num_sort[index])
ni_num_sum[index] = ni_num_sum_temp[i] + (j - mid - 1);
num_sort[index] = num[i];
++index;
++i;
}
while(j <= r){
if(num[i] != num_sort[index])
ni_num_sum[index] = ni_num_sum_temp[j] + (mid - i + 1);
num_sort[index] = num[j];
++index;
++j;
}
// 放回
for(i = l; i <= r; i++){
num[i] = num_sort[i];
ni_num_sum_temp[i] = ni_num_sum[i]; // 緩存上一次的逆序對數
}
}
void gui_pai(int l, int r){
if(l < r){
int mid = (l + r) >> 1;
gui_pai(l, mid);
gui_pai(mid + 1, r);
pai_sort(l, mid, r);
}
}
// 這裏需要使用數字的最大值存放數據, 因爲這相等於揹包
#define MAX 1000005
// 樹狀數組解法
// 需要兩個數組
long tree_num_left[MAX];
long tree_num_right[MAX];
// 存放求和結果
long ans_num_left[MAX];
long ans_num_right[MAX];
// 存放最大方便確定掃描區間 減少複雜度
long max_num = -999999;
inline int low_bit(int t){
return t&(-t);
}
// 求和
inline long tree_sum(long tree_num[], long index){
long sum = 0;
for(long i= index; i > 0; i -=low_bit(i)){
sum += tree_num[i];
}
return sum;
}
// 更新單節點
inline void update(long tree_num[], int index, long num){
for(int i = index; i <= max_num; i += low_bit(i)){
tree_num[i] += num;
}
}
int main(){
scanf("%d", &N);
for(int i = 0; i < N; i++){
scanf("%d", num + i);
if(num[i] > max_num){
max_num = num[i];
}
}
max_num ++; // 因爲會出現0的情況, 將最大值加1就可以, 相當於向右偏移1, 當然 多加也無所謂
// 樹狀數組解法
// 更新 由左向右掃描
for(int i = 0; i < N; ++i){
update(tree_num_left, num[i] + 1, 1);
// 若是 替換成 ans_num_right[num[i]] 數據將變得有序 , 但是後續合併和計算複雜度 最差 將會變高10倍
// 而且並不需要有序, 只需要結果就好
ans_num_left[i] = tree_sum(tree_num_left, max_num) - tree_sum(tree_num_left, num[i] + 1);
}
// 更新 由右向左掃描
for(int i = N - 1; i >= 0; --i){
update(tree_num_right, num[i] + 1, 1);
ans_num_right[i] = tree_sum(tree_num_right, num[i]);
}
// 合併
unsigned long long ans_2 = 0;
for(int i = 0; i < N; ++i){
ans_num_right[i] += ans_num_left[i];
// 求值
ans_2 += sum_0_to_n(ans_num_right[i]);
printf("%ld ", ans_num_right[i]);
}
puts("");
printf("樹狀數組求得:%lld\n", ans_2);
gui_pai(0, N - 1);
// 使用逆序對 計算結果
for(int i = 0; i < N; ++i){
ans += sum_0_to_n(ni_num_sum[i]);
}
for(int i = 0; i < N; ++i){
printf("%ld ", ni_num_sum[i]);
}
puts("");
printf("歸併排序求得: %lld\n", ans);
return 0;
}
運行截圖: