[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
",ans); 
40         }
41     return 0; 
42 }
View Code
原文地址:https://www.cnblogs.com/MekakuCityActor/p/9888584.html