zoukankan      html  css  js  c++  java
  • 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 }
  • 相关阅读:
    第一篇:白话tornado源码之一个脚本引发的血案
    第二篇:白话tornado源码之待请求阶段
    Python 面向对象(初级篇)
    python 面向对象(进阶篇)
    Csharp启动exe文件
    UNIX时间戳
    JS日期格式化代码
    Android之什么是Activity和常用的ADB命令以及Android项目结构的认识
    thread同步测试
    Fluent NHibernate使用小结:(1)通用配置文件创建方法
  • 原文地址:https://www.cnblogs.com/algorithms/p/2496482.html
Copyright © 2011-2022 走看看