COJ1113(Emperor And His Knight)

题目大意:给定一个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 }
原文地址:https://www.cnblogs.com/algorithms/p/2496482.html