拨钟问题-枚举

OpenJudge-NOI/2.1基本算法之枚举-1816:拨钟问题

1816:拨钟问题

总时间限制: 1000ms 内存限制: 65536kB

描述

有9个时钟,排成一个3*3的矩阵。

|-------|    |-------|    |-------|
|       |    |       |    |   |   |
|---O   |    |---O   |    |   O   |
|       |    |       |    |       |
|-------|    |-------|    |-------|
    A            B            C    

|-------|    |-------|    |-------|
|       |    |       |    |       |
|   O   |    |   O   |    |   O   |
|   |   |    |   |   |    |   |   |
|-------|    |-------|    |-------|
    D            E            F    

|-------|    |-------|    |-------|
|       |    |       |    |       |
|   O   |    |   O---|    |   O   |
|   |   |    |       |    |   |   |
|-------|    |-------|    |-------|
    G            H            I    
              (1)

现在需要用最少的移动,将9个时钟的指针都拨到12点的位置。共允许有9种不同的移动。如下表所示,每个移动会将若干个时钟的指针沿顺时针方向拨动90度。

移动    影响的时钟
 
 1         ABDE
 2         ABC
 3         BCEF
 4         ADG
 5         BDEFH
 6         CFI
 7         DEGH
 8         GHI
 9         EFHI    

输入

9个整数,表示各时钟指针的起始位置,相邻两个整数之间用单个空格隔开。其中,0=12点、1=3点、2=6点、3=9点。

输出

输出一个最短的移动序列,使得9个时钟的指针都指向12点。按照移动的序号从小到大输出结果。相邻两个整数之间用单个空格隔开。

样例输入

3 3 0 
2 2 2 
2 1 2 

样例输出

4 5 8 9 

题解

由题可知,一共有9种不同的移动,那么我们将每一种移动对应于一种操作(operation),简写成op。每种操作都会影响若干个时钟,那么我们将每种操作影响的时钟转换为每个时钟受哪些操作的影响。如下所示:

A 1 2 4
B 1 2 3 5
C 2 3 6
D 1 4 5 7
E 1 3 5 7 9
F 3 5 6 9
G 4 7 8
H 5 7 8 9
I 6 8 9

又因为每次操作会将若干个时钟转动90度,所以当同一个操作进行4次,其实就是旋转了360度,相当于没有操作。(一种操作做四次和不做是一样的)所以每种操作做的次数为:0,1,2,3。一共有9种操作,所以枚举次数为4的9次方,暴力枚举就是一个9重循环。最后用一个变量sum来累加每个时钟被操作后的指向,如果累加完后sum=0,那么说明找到了一个移动序列使得9个时钟的指针都指向12点。但此时我们并不知道找到的是否就是最短的序列,所以还要进行比较取操作次数的最小值。

方法一 暴力枚举

代码如下:

#include
using namespace std;
int ori[10];
int op[10];
int result[10];
int main()
{
	int sum,min=28,moves;
	for(int i=1; i<10; ++i)
	  cin >> ori[i];
	for(op[1]=0; op[1]<4; ++op[1])
	  for(op[2]=0; op[2]<4; ++op[2])
	    for(op[3]=0; op[3]<4; ++op[3])
	      for(op[4]=0; op[4]<4; ++op[4])
	        for(op[5]=0; op[5]<4; ++op[5])
	          for(op[6]=0; op[6]<4; ++op[6])
	            for(op[7]=0; op[7]<4; ++op[7])
	              for(op[8]=0; op[8]<4; ++op[8])
	                for(op[9]=0; op[9]<4; ++op[9])
					{
					    sum=0;
					    sum+=(ori[1]+op[1]+op[2]+op[4])%4;
					    sum+=(ori[2]+op[1]+op[2]+op[3]+op[5])%4;
					    sum+=(ori[3]+op[2]+op[3]+op[6])%4;
					    sum+=(ori[4]+op[1]+op[4]+op[5]+op[7])%4;
					    sum+=(ori[5]+op[1]+op[3]+op[5]+op[7]+op[9])%4;
					    sum+=(ori[6]+op[3]+op[5]+op[6]+op[9])%4;
					    sum+=(ori[7]+op[4]+op[7]+op[8])%4;
					    sum+=(ori[8]+op[5]+op[7]+op[8]+op[9])%4;
					    sum+=(ori[9]+op[6]+op[8]+op[9])%4;
					    if(sum==0)
					    {	
					    	moves=0;
					    	for(int i=1; i<10; ++i)
					    		moves+=op[i];
							if(moves<min)
							{
								min=moves;
								for(int i=1; i<10; ++i)
									result[i]=op[i];
							}	
					    }
					}
	for(int i=1; i<10; ++i)
	{
		while(result[i]--)
		{
			cout << i << " ";
		}
	}
	return 0;
}

枚举的核心问题就是:
1.怎样去枚举?
2.在答案正确的情况下,怎么减少枚举次数?
所以这道题怎么减少枚举次数呢?
枚举中有一个十分重要的思想—局部思想法,它的基本思路如下:
如果存在某个局部, 一旦这个局部的状态被确定, 那么剩余其他部分的状态只能是确定的一种, 或者不多的n 种, 那么就只需枚举这个局部的状态即可。
所以我们来看这道题,看能不能找到一个局部使得枚举的次数减少。不难发现操作1,2,3为一个局部。例如,当我们确定了操作1,2,3的次数以后,得到A,B,C这三个时钟的指针的指向,此时只有操作4能够改变A时钟的的指针方向,使它能指向12点。同理,只有操作5才能够改变B时钟的指针方向,只有操作6才能够改变C时钟的指针方向。那么操作4-6的次数也就确定了。同样,继续往下,现在操作1-6的次数都确定了,只有操作7才能改变D时钟的指针方向,只有操作8才能改变G时钟的指针方向,只有操作9才能改变F时钟的指针方向。
这样操作1-9的次数都被确定了,A,B,C,D,G,F这6个时钟的指针都指向了12点,只有E,H,I这三个时钟指针方向还没有确定。所以剩下只用判断E,H,I这三个时钟的指针指向12点没有,如果都指向了12点,那说明找到了一个序列使得9个时钟的指针都指向了12点。
所以根据局部思想的方法,我们只需对操作1,2,3进行枚举,枚举的次数为4的3次方=64次。

方法二 局部枚举

代码如下:

#include
using namespace std;
int ori[10];
int op[10];
int result[10];
int main()
{
	int min=28,moves,e,h,i;
	for(int i=1; i<10; ++i)
	  cin >> ori[i];
	for(op[1]=0; op[1]<4; ++op[1])
	  for(op[2]=0; op[2]<4; ++op[2])
	    for(op[3]=0; op[3]<4; ++op[3])
		{
			op[4]=(4-(ori[1]+op[1]+op[2])%4)%4;//确定A
			op[5]=(4-(ori[2]+op[1]+op[2]+op[3])%4)%4;//确定B
			op[6]=(4-(ori[3]+op[2]+op[3])%4)%4;//确定C
			op[7]=(4-(ori[4]+op[1]+op[4]+op[5])%4)%4;//确定D
			op[8]=(4-(ori[7]+op[4]+op[7])%4)%4;//确定G
			op[9]=(4-(ori[6]+op[3]+op[5]+op[6])%4)%4;//确定F
			e=(ori[5]+op[1]+op[3]+op[5]+op[7]+op[9])%4;
			h=(ori[8]+op[5]+op[7]+op[8]+op[9])%4;
			i=(ori[9]+op[6]+op[8]+op[9])%4;
			if((e+h+i)==0)//判断E,H,I这三个时钟指针都指向12点没有
			{
				moves=0;
				for(int i=1; i<10; ++i)
					moves+=op[i];
				if(moves<min)
				{
					min=moves;
					for(int i=1; i<10; ++i)
						result[i]=op[i];
				}		
			}
		}
	for(int i=1; i<10; ++i)
	{
		while(result[i]--)
		{
			cout << i << " ";
		}
	}
	return 0;			    
}

感谢观看!如有错误,还请指出。
此题来源于http://noi.openjudge.cn/ch0201/1816/
同时我也向大家推荐中国大学MOOC上的来自北京大学郭炜老师的课程—程序设计与算法(二)算法基础。
拨钟问题-枚举_第1张图片

你可能感兴趣的:(算法基础,NOI)