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 }

 

你可能感兴趣的:(ror)