Flip and Shift -- ACM PKU 1063 解题报告

问题描述:This puzzle consists of a random sequence of m black disks and n white disks on an oval-shaped track, with a turnstile capable of flipping (i.e., reversing) three consecutive disks or shifting one position clockwise for each of the disks on the track. The goal of this puzzle is to gather the disks of the same color in adjacent positions using flips and shifts. You are to write a program which decides whether a given sequence can reach a goal or not. If a goal is reachable, then write a message "YES"; otherwise, write a message "NO".

详见:http://acm.pku.edu.cn/JudgeOnline/problem?id=1063

我试着自己翻译一下题意(意译式的):在日本有一种旋转寿司店,厨师把做好的寿司放到一个环形道上,而环形道在直在旋转,这样,围着环形道坐的顾客就能品味到不同的寿司。现在,假设在一个 L 个单位长的环形道上,每个单位长度放着一个或黑或白的盘子。环形道本身并不自己旋转,而是要靠一个机关来操作。这个机关固定在某个位置。它可以把它前面的三个连续的盘子反转位置,或者把环形道顺时针移动一个单位距离。例如,如果当前序列为 1 2 3 ... L,其中数字为盘子编号,而假定机关就在 3 号位置上,如果机关使用反转功能,则序列变成 1 4 3 2 5 ... L (注意黑体部分);如果使用了移动功能,则序列变成 L 1 2 3 4 ... L-1 ,现在摆在机关面前的是 2 号盘子。这里给盘子编号,是为了说明和举例,原题描述中并无编号。现在的问题是,给了一个盘子序列,要通过操作机关来把所有黑盘调整到一块,而所有白盘也在一块。但不是所有序列都能这么黑白两分的,比如序列 黑白黑白 就不能两分。写一个程序,接受一个盘子序列为输入,确定该序列是否可以黑白两分。

看问题要看本质。让我们先看看这个 turnstitle 到底能“真正”做些什么。连续的三个盘子,可能的情况是:1) ooo 2) xox 3)oxx 4)xxo 其中, x 和 o 表示不同颜色的盘子。除此之后,没有其它可能了。对于前两种情况,使用了 turnstitle 的 flip 功能和没使用是一样的,因此不起作用。那么,真正有用的就是后两者情况了。扳动了 turnstitle 的 flip 功能, 就把 3)变成 4),或者把 4) 变成 3)。本质上,就是把连续的两个 xx 向逆时针或顺时针方向前进一格,同时把在它们正前面的 o 抛到后面去。比如 3),本质上就 xx 向左(逆时针)移动了一格,然后把它们前面,即 xx 左边的那个 o 挪到 xx 的右边。而 4)的情况正好是左右互换。再结合 shift 功能,我们就可以把连续的两个 xx 向左或向右移动任意数量的格子!

基于以上本质上的观察,要把黑的和白的分开,那么,我们从 track 的任一处开始,把所有连续的两个 xx 移动到一起,如果最后的结果是 xxx... ooo... 则成功了,否则,序列不可以被黑白两分。例如 oxoxox 这些类型的序列,就不能黑白两分的。事实上,可以发展一些严格的数学定义来说明,只有这一类型的序列不能黑白两分,其它不能两分的都能转成这种类型的(同构)。简单地说,就是对于 xx,我们可以把它们从序列中删掉,而不影响序列的可分性。删除这些 xx 之后,序列要么为空,要么为 o,要么 ox,要么为 oxox...ox。前两者即为可分,后者显然不可分。当我们写程序时,实际碰到的还有另一种情况,即 oxox...oxo (长度为奇数,且 o 和 x 交替着出现)。事实上,它就是 o,因为 track 是环状的,它可以通过继续删除 xx 这种组合,从而最后变成 o 类型。

这样,我们只需要从头开始扫描一遍序列,如果当前的盘子和前一个盘子颜色相同,则删除掉当前盘子和前一个盘子,否则,当前的序列长度增加 1。当序列扫描结束时,如果序列的长度不大于 2 或长度为奇数,则可分,否则,不可分。

程序不长,我就打破我的惯例,贴一下我写的C代码吧。我一般不贴代码,一觉得没必要,二是觉得自己的代码很难看,不想贻笑于大方。

#include<stdio.h> int main() { int T,mn,c,top,dish; scanf("%d",&T); while(T) { c = 0; scanf("%d",&mn); while(mn) { scanf("%d",&dish); if(c==0 || top != dish) { top = dish; c++; } else { top = top ? 0 : 1; c--; } mn--; } if(c<=2 || c%2) printf("YES/n"); else printf("NO/n"); T--; } return 1; }

其中,T 为测试用例的数量,mn为序列长度, c 为当前已读序列并结合了删除操作的序列长度,dish 是当前盘子的颜色,top 是前一个盘子的颜色。

 

你可能感兴趣的:(c,测试,Random)