[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 }