IOI'96 Magic Square解题报告

IOI‘96 Magic Square(魔版)

题目由JerryXie翻译。

 

Magic Square(msqaure.cpp/c/pas)

【问题描述】

        在魔方的成功之后,卢比克先生发明了它的二维版本,叫做魔版。它是一个由八个方块组成的表格。

        

        在本题中我们认为每一个方块都有不同的颜色,这些颜色将会用1~8共8个整数表示。一个表格的状态将会以一个由颜色组成的序列表示,顺序是从表格左上角沿顺时针方向给出。比如上面这个表格,序列为(1,2,3,4,5,6,7,8),这被称作初始状态。

        魔版有三种基本变换,用'A', 'B', 'C'表示,可以改变魔版的状态。

        A:将第一行和第二行交换;

        B:将最右侧的一列插入最左侧;

        C:将中间两列沿顺时针旋转90度。

        下面是对初始状态的三种变化:

        

        所有可能的状态都能使用这三种基本变换。

        你需要编写一个程序来计算从初始状态变化为指定状态所需要的最短的操作序列。

 

【输入】

        输入文件名为msquare.in。

        输入文件只有一行,共有8个数,每个数用空格隔开,为指定状态的颜色序列(用1~8表示)。

 

【输出】

        输出文件名为msquare.out。

        Line 1::一个整数,表示最短的操作序列的长度。

        Line 2:是字典序中最早出现的操作序列。除最后一行外每行60个字符。

 

【输入输出样例】

msquare.in

msquare.out

2 6 8 4 5 7 3 1 

7

BCABCCB

 

 

 

 

 

 

【题目分析】

        分析题目,我们可以发现这完全可以用BFS解决。而且要求输出字典序第一个序列,这样我们可以通过BFS搜索到第一个解就可以退出搜索。搜索方法是:从初始状态开始,分别进行A,B,C三种操作得到三个状态然后进入队列,再从队头取出一个状态重复上述操作,最终找到解。而操作序列的长度则为搜索所得出的解的结点在解答树中的深度(根节点即初始状态深度为0)。而判重则可以使用康托展开处理,用标记数组标记此状态是否已经搜索过,如果搜索过则不入队,在入队的过程中我们需要记录一下当前的操作和父结点,方便返回时能够输出操作序列。还有,我们从输出格式中的Line2发现可能序列不止一行,而在测试后我们发现最长的操作序列只有20多个操作,所以在下面给出的代码中没有涉及到60个字符换行的问题,如果需要可以在输出的位置进行补充。

 

【程序源代码】

 

 

 
  1. //msquare.cpp by JerryXie   
  2.   
  3. #include<cstdio>   
  4. using namespace std;   
  5.   
  6. struct queue //定义结构体用来表示队列    
  7. {   
  8.        int cantor,pre;   
  9.        char ope;   
  10. }q[50001];   
  11.   
  12. int a[10],direct[10],bin[11],original[10];   
  13. bool had[50001]; //判重标记数组    
  14.   
  15. void operation(char c) //执行A~C三种操作    
  16. {   
  17.      int i,j,t;   
  18.      if(c=='A')   
  19.        for(i=1,j=8;i<j;i++,j--)   
  20.        {   
  21.          t=a[i];   
  22.          a[i]=a[j];   
  23.          a[j]=t;   
  24.        }   
  25.      else if(c=='B')   
  26.      {   
  27.        t=a[4];   
  28.        for(i=4;i>=2;i--)   
  29.          a[i]=a[i-1];   
  30.        a[1]=t;   
  31.        t=a[5];   
  32.        for(i=5;i<=7;i++)   
  33.          a[i]=a[i+1];   
  34.        a[8]=t;   
  35.      }   
  36.      else if(c=='C')   
  37.      {   
  38.        t=a[3];   
  39.        a[3]=a[2];   
  40.        a[2]=a[7];   
  41.        a[7]=a[6];   
  42.        a[6]=t;   
  43.      }   
  44.      else;   
  45. }   
  46.   
  47. void create() //初始化初始状态和0~10的阶乘值    
  48. {   
  49.      int i;   
  50.      for(i=1;i<=8;i++)   
  51.        a[i]=i;   
  52.      bin[0]=1;   
  53.      for(i=1;i<=10;i++)   
  54.        bin[i]=bin[i-1]*i;   
  55.      return;   
  56. }   
  57.   
  58. int xtonum(int x[]) //把排列转换为值(康托展开)    
  59. {   
  60.     int b[10]={0},i,j,num=0;   
  61.     for(i=1;i<=8;i++)   
  62.       for(j=i+1;j<=8;j++)   
  63.         if(x[i]>x[j])   
  64.           b[i]++;   
  65.     for(i=1;i<=8;i++)   
  66.       num+=b[i]*bin[8-i];   
  67.     return num;   
  68. }   
  69.   
  70. void translate(int b[]) //把计算出的排列转换为原始排列    
  71. {   
  72.      int i,j,s;   
  73.      bool t[10];   
  74.      for(i=1;i<=8;i++)   
  75.        t[i]=true;   
  76.      a[1]=b[1]+1;   
  77.      t[a[1]]=false;   
  78.      for(i=2;i<=8;i++)   
  79.      {   
  80.        s=0;   
  81.        for(j=1;j<=8;j++)   
  82.          if(t[j]==true)   
  83.          {   
  84.            s++;   
  85.            if(s==b[i]+1)   
  86.            {   
  87.              a[i]=j;   
  88.              t[j]=false;   
  89.              break;   
  90.            }   
  91.          }   
  92.      }   
  93. }   
  94.   
  95. void numtoa(int num) //把值转换为排列(逆康托展开)    
  96. {   
  97.      int i,yushu,div,temp=num,b[10];   
  98.      for(i=1;i<=8;i++)   
  99.      {   
  100.        div=temp/bin[8-i];   
  101.        yushu=temp%bin[8-i];   
  102.        b[i]=div;   
  103.        temp=yushu;   
  104.      }   
  105.      b[8]=0;   
  106.      translate(b);   
  107.      return;   
  108. }   
  109.   
  110. bool IsHad(int num) //判重,判断队列中是否存在当前值,如果有则视为已经扩展过此结点,不需入队进行扩展    
  111. {   
  112.      if(had[num]==true)   
  113.        return true;   
  114.      else  
  115.        return false;   
  116. }   
  117.   
  118. void print(int qnail) //输出解    
  119. {   
  120.      char o[50001],i,onum=0;   
  121.      queue t=q[qnail];   
  122.      while(t.pre!=-1) //从解的结点逆推回根结点,记录中间的操作    
  123.      {   
  124.        o[++onum]=t.ope;   
  125.        t=q[t.pre];   
  126.      }   
  127.      printf("%d\n",onum); //输出序列长度    
  128.      for(i=onum;i>=1;i--) //输出操作序列,由于记录是从解的结点逆推回根结点的,所以输出也应逆过来,表示从根结点到解的结点的操作序列    
  129.        printf("%c",o[i]);   
  130.      printf("\n"); //换行符(USACO要求输出文件最后应有一个空行)    
  131.      return;   
  132. }   
  133.   
  134. int main()   
  135. {   
  136.     freopen("msquare.in","r",stdin);   
  137.     freopen("msquare.out","w",stdout);   
  138.     int i,final,nowcantor,qhead=1,qnail=0; //初始化队头和队尾位置    
  139.     bool sign=true; //搜索标记    
  140.     char c;   
  141.     create(); //初始化   
  142.     for(i=1;i<=8;i++) //读入目标状态    
  143.       scanf("%d",&direct[i]);   
  144.     final=xtonum(direct); //计算目标状态的康托展开值    
  145.     nowcantor=xtonum(a); //计算初始状态的康托展开值    
  146.     if(nowcantor==final) //如果目标状态就是初始状态    
  147.     {   
  148.       printf("0\n\n"); //输出0,因为操作序列长度为0,所以空出一行    
  149.       sign=false; //更改搜索标记,之后不需搜索    
  150.     }   
  151.     else //如果不是则入队    
  152.     {   
  153.       q[++qnail].cantor=nowcantor;   
  154.       q[qnail].pre=-1; //置父节点为-1    
  155.       q[qnail].ope=0; //无操作    
  156.     }   
  157.     while(sign) //开始搜索    
  158.     {   
  159.       numtoa(q[qhead].cantor); //提取队头的康托展开值然后还原为状态    
  160.       for(i=1;i<=8;i++) //保存当前状态    
  161.         original[i]=a[i];   
  162.       for(c=65;c<=67;c++) //从A~C循环('A'的ASCII码值为65,'C'的ASCII码值为67)    
  163.       {   
  164.         operation(c); //从A~C依次进行基本变换    
  165.         nowcantor=xtonum(a); //记录变换后的康托展开值    
  166.         if(nowcantor==final) //如果变换后与目标状态相同    
  167.         {   
  168.           q[++qnail].cantor=nowcantor; //入队    
  169.           q[qnail].ope=c;   
  170.           q[qnail].pre=qhead;   
  171.           had[nowcantor]=true; //记录已经使用    
  172.           print(qnail); //输出结果    
  173.           sign=false; //修改搜索标记,退出搜索    
  174.           break; //退出当前循环    
  175.         }   
  176.         else //如果不相同    
  177.         {   
  178.           if(IsHad(nowcantor)==false) //如果之前没有扩展过    
  179.           {   
  180.             q[++qnail].cantor=nowcantor; //入队    
  181.             q[qnail].ope=c; //记录由父结点变换到当前状态所使用的变换操作    
  182.             q[qnail].pre=qhead; //记录父结点的位置    
  183.             had[nowcantor]=true; //记录已经使用    
  184.           }   
  185.         }   
  186.         if(c=='A' || c=='B') //如果变换操作为A~B则使a数组还原为未变换之前的状态,以进行下一个操作    
  187.           for(i=1;i<=8;i++)   
  188.             a[i]=original[i];   
  189.       }   
  190.       qhead++; //扩展后出队    
  191.     }   
  192.     return 0;   
  193. }   

 

        本程序在USACO上测试通过。

        

 

欢迎鄙视。

posted @ 2015-09-03 20:59  JerryXie  阅读(237)  评论(0编辑  收藏  举报