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

本程序在USACO上测试通过。

欢迎鄙视。

IOI'96 Magic Square解题报告

时间: 2024-12-18 07:58:02

IOI'96 Magic Square解题报告的相关文章

[CF335D]Rectangle And Square 解题报告

中文题面: [描述] 你有N个矩形(编号从1到N).所有矩形的四个角的坐标都是整数,并且两组对边分别平行于X和Y两坐标轴.不同的矩形可能接触,但是不会重叠. 现在你的任务是:选出一些矩形,使它们恰好拼成一个正方形. [输入] 第一行,一个整数N,为矩形的数目. 以下N行,每行四个整数x1, y1, x2, y2,描述一个左下角为(x1, y1),右上角为(x2, y2)的矩形.这N行中的第i行描述的是编号为i的矩形. [输出] 如果存在这样的子集: 在第一行输出"YES",后接一个空格

解题报告 之 SOJ3191 Free square

解题报告 之 SOJ3191 Free square Description Time Limit: 5000 MS Memory Limit: 65536 K Description A positive integer is said to be squarefree if it is divisible by no perfect square larger than 1. For example, the first few squarefree numbers are {1, 2, 3

NOIP2015 解题报告

过了这么久才来发解题报告,蒟蒻实在惭愧 /w\ Day1 T1 [思路] 模拟 [代码] 1 #include<iostream> 2 #include<cstring> 3 #include<queue> 4 #include<cmath> 5 #define FOR(a,b,c) for(int a=(b);a<=(c);a++) 6 using namespace std; 7 8 const int maxn = 50; 9 10 int G[

解题报告 之 POJ3057 Evacuation

解题报告 之 POJ3057 Evacuation Description Fires can be disastrous, especially when a fire breaks out in a room that is completely filled with people. Rooms usually have a couple of exits and emergency exits, but with everyone rushing out at the same time

CH Round #56 - 国庆节欢乐赛解题报告

最近CH上的比赛很多,在此会全部写出解题报告,与大家交流一下解题方法与技巧. T1 魔幻森林 描述 Cortana来到了一片魔幻森林,这片森林可以被视作一个N*M的矩阵,矩阵中的每个位置上都长着一棵树,其中一些树上结有能够产生能量的魔力水果.已知每个水果的位置(Xi,Yi)以及它能提供的能量Ci.然而,魔幻森林在某些时候会发生变化:(1) 有两行树交换了位置.(2) 有两列树交换了位置.当然,树上结有的水果也跟随着树一起移动.不过,只有当两行(列)包含的魔力水果数都大于0,或者两行(列)都没有魔

Codeforces Round #259 (Div. 2) 解题报告

终于重上DIV1了.... A:在正方形中输出一个菱形 解题代码: 1 // File Name: a.cpp 2 // Author: darkdream 3 // Created Time: 2014年08月01日 星期五 23时27分55秒 4 5 #include<vector> 6 #include<set> 7 #include<deque> 8 #include<stack> 9 #include<bitset> 10 #inclu

BZOJ 1051 最受欢迎的牛 解题报告

题目直接摆在这里! 1051: [HAOI2006]受欢迎的牛 Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 4438  Solved: 2353[Submit][Status][Discuss] Description 每一头牛的愿望就是变成一头最受欢迎的牛.现在有N头牛,给你M对整数(A,B),表示牛A认为牛B受欢迎. 这 种关系是具有传递性的,如果A认为B受欢迎,B认为C受欢迎,那么牛A也认为牛C受欢迎.你的任务是求出有多少头 牛被所有的牛

习题:codevs 1035 火车停留解题报告

本蒟蒻又来写解题报告了.这次的题目是codevs 1035 火车停留. 题目大意就是给m个火车的到达时间.停留时间和车载货物的价值,车站有n个车道,而火车停留一次车站就会从车载货物价值中获得1%的利润,让你来求一种安排方法,使得车站收益最大,并输出收益值. 蒟蒻的思路是这样的: 一眼看出:最大费用最大流(MCMF)显然cost:表示车站收益然后……以车站为点建立图求流?同一个车站可能经过好几辆火车……,貌似很麻烦……:那么以什么建图.连边,还有怎么连?貌似有点类似于方格取数2之中的拆点……:那么

洛谷OJ P1379 八数码难题 解题报告

洛谷OJ P1379 八数码难题 解题报告 by MedalPluS 题目描述   在3×3的棋盘上,摆有八个棋子,每个棋子上标有1至8的某一数字.棋盘中留有一个空格,空格用0来表示.空格周围的棋子可以移到空格中.要求解的问题是:给出一种初始布局(初始状态)和目标布局(为了使题目简单,设目标状态为123804765),找到一种最少步骤的移动方法,实现从初始布局到目标布局的转变.   输入格式   输入初试状态,一行九个数字,空格用0表示   输出格式 只有一行,该行只有一个数字,表示从初始状态到