题目大意:给定一个1-n的排列,规定一种操作,每次只能交换相邻的两个数,求至少进行多少次操作,能将给定的初始态转为目标态,目标态定义为序列中除1外任何一个数都比其左边相邻的那个数大(如果存在),1的左边相邻的数只能是n或者没有。
分析:先考虑简单的情况,将初始态转为1,2,3...n。注意到1,2,3...n的逆序数为0,对任何一个序列进行一次上述操作逆序数会增1或减1,如果能保证每次操作都使逆序数减1,那么最少的操作次数便是逆序数,事实正是如此,下面简单证明:当一个序列的逆序数大于0时,则必存在a,b使得a在b的左边且a>b,如果a和b相邻,则交换a,b即可,如果a和b不相邻,则必存在c在a,b之间,已知a,c和c,b必有一组构成逆序对,若这对逆序对仍不相邻,则可照上继续下去,总可以找打相邻的两个数构成逆序对,交换即可。终上所述,将1-n的一个排列转为1,2,3...n最少需操作的次数即为排列的逆序数。那其他的目标态呢?容易知道,目标态一共有n种(目标态的第一个数确定便可确定目标态),例如以2开头的目标态为2,3...n,1,我们把1看成n+1问题便解决了,其他情况类似。
需要注意的是,n最大为100000,求逆序数直接求的话复杂度为O(N2),我是利用树状数组来求,用归并排序应该也可。还有一点需要注意,最后的结果会超32位。
View Code
1 #include <stdio.h> 2 #include <string.h> 3 #define N 100001 4 int a[N],c[N],id[N],n; 5 void update(int k) 6 { 7 for(int i=k;i<=n;i+=(-i&i)) c[i]++; 8 } 9 int getsum(int k) 10 { 11 int sum=0; 12 for(int i=k;i>0;i-=(-i&i)) sum+=c[i]; 13 return sum; 14 } 15 int main() 16 { 17 int i; 18 long long cnt,ans; 19 while(~scanf("%d",&n)) 20 { 21 memset(c,0,sizeof(c)); 22 cnt=0; 23 for(i=1;i<=n;i++) 24 { 25 scanf("%d",&a[i]); 26 id[a[i]]=i; 27 cnt+=i-1-getsum(a[i]); 28 update(a[i]); 29 } 30 ans=cnt; 31 for(i=1;i<n;i++) 32 { 33 if((cnt=cnt-(id[i]-1)+(n-id[i]))<ans) ans=cnt; 34 } 35 printf("%lld\n",ans); 36 } 37 return 0; 38 }