1. 程式人生 > >[zoj4046][樹狀陣列求逆序(強化版)]

[zoj4046][樹狀陣列求逆序(強化版)]

http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=4046

題意:有一個含有n個元素的數列p,每個元素均不同且為1~n中的一個,求出將數列變為迴圈遞增序列至少需要左右相鄰的數交換多少次

題目分析:先看簡化版的題目:如果只有1 2 3 4 5是符合要求的,那麼交換次數根據氣泡排序可得就是逆序數,直接樹狀陣列求逆序數即可.

由於題目要求只要只要是迴圈遞增數列即可,也就是1 2 3 4 5 和 2 3 4 5 1都是符合要求的.那麼就要列舉數字1出現的位置了,如果暴力求解顯然會TLE,而可以發現1 2 3 4 5 和2 3 4 5 1所需交換次數的差就是(n - pos[ 1 ] )- (pos[ 1 ] - 1 )(其中pos[ i ]表示數字i的起始位置),因為不管1起始位置在哪,得到1 2 3 4 5的時候都可以先把1移動到第一個位置,然後移動2 3 4 5的相對位置,得到2 3 4 5 1的時候都可以先把1移動到最後一個位置,然後移動2 3 4 5的相對位置,所以移動成1 2 3 4 5與移動成2 3 4 5 1的次數之差就是 (n - pos[ 1 ] )- (pos[ 1 ] - 1 ).正是因為可以分別把數字移動到首位置和末位置,才可以直接根據(n - pos[ i ] )- (pos[ i ] - 1 )計算差值,所以需要分別計算1 2 3 4 5    2 3 4 5 1     3 4 5 1 2       4 5 1 2 3       5 1 2 3 4

 1 #include <iostream>
 2 #include <iomanip>
 3 #include <algorithm>
 4 #include <map>
 5 # include <bits/stdc++.h>
 6 using namespace std;
 7 typedef long long LL;
 8 const int maxn = 1e5+30;
 9 int n, id[maxn], sum[maxn];
10 void add(int x){ 
11     for
(;x<=n;x+=x&-x) ++sum[x]; 12 } 13 int query(int x){ 14 int res = 0; 15 for(;x>0;x-=x&-x) res += sum[x]; 16 return res; 17 } 18 19 int main(){ 20 int T, x; 21 for(scanf("%d",&T);T;--T){ 22 scanf("%d",&n); 23 for(int i=1; i<=n; ++i){
24 scanf("%d",&x); 25 id[x] = i; 26 sum[i] = 0; 27 } 28 LL tot = 0, ans; 29 for(int i=n; i>0; --i){ 30 tot += query(id[i]); 31 add(id[i]); 32 } 33 ans = tot; 34 for(int i=1; i<=n;++i){ 35 tot += n-id[i]; 36 tot -= id[i]-1; 37 ans = min(ans, tot); 38 } 39 printf("%lld\n",ans); 40 } 41 return 0; 42 }
View Code