POJ 拨钟问题

关于枚举算法的一些心得总结。

题目描述:有9个时钟,排成一个3*3的矩阵。

现在需要用最少的移动,将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

相比与熄灯问题,拨钟问题更具一般性.

思路如下:

枚举第一行的所有情况,由于每个时钟块可以被拨动不止一次,所以变量应为每个时钟块的拨动次数,而次数有0、1、2、3四种情况。所以需要枚举4^3次。每一次枚举完,确定好第一行每一块的拨动次数后,下一步就是以此为基础,确定全部时钟的拨动次数(熄灯问题也是如此)。那么如何来确定呢?

ABCDEFGHI时钟对应的拨动次数是i1 i2 i3 i4 i5 i6 i7 i8 i9

现在已知i1 i2 i3 且易知,A时钟是否被调至零点只与A B D时钟的拨动次数有关,现在已知了A B的拨动次数,为了将A调零,可以求出D的拨动次数。    

即 i4 = (4 - (puzzle[1][1] + i2 + i1) % 4) % 4;

注意,此式代表了两层信息,首先是A时钟可以被调至零,还有就是借此算出了D的拨动次数

(理清这一点很重要,枚举算法的最后其实就是去验证那些在前面没有被验证的元素是否可以被调零。此处A时钟已经被确定可以为零,所以最后不用检验A时钟)

同理可以求出其他所有时钟的拨动次数,但由于最开始假设了三个已知量,所以其实只验证了6个时钟是否可以被调零,还剩下3个没有验证,所以这三个就是算法最后需要去验证的。

这部分具体代码如下

int i1, i2, i3, i4, i5, i6, i7, i8, i9;

    i4 = (4 - (puzzle[1][1] + i2 + i1) % 4) % 4;
	// 确定了1行1列元素被置零,同时求出i4
	i5 = (4 - (puzzle[1][2] + i1 + i2 + i3) % 4) % 4;
	// 确定了1行2列元素被置零,同时求出i5
	i6 = (4 - (puzzle[1][3] + i2 + i3) % 4) % 4;
	// 确定了1行3列元素被置零,同时求出i6
	i7 = (4 - (puzzle[2][1] + i4 + i1 + i5) % 4) % 4;
	// 确定了2行1列元素被置零,同时求出i7
	i8 = (4 - (puzzle[3][1] + i7 + i4) % 4) % 4;  
	// 确定了3行1列元素被置零,同时求出i8
	i9 = (4 - (puzzle[2][2] + i5 + i1 + i3 + i7) % 4) % 4;
	// 确定了2行2列元素被置零,同时求出i9

求出所有时钟的拨动次数后,就验证最后三个时钟在现有的“环境”中是否可以被调零

如下:

if ((puzzle[2][3] + i3 + i9 + i5 + i6) % 4 == 0
	&& (puzzle[3][2] + i5 + i7 + i8 + i9) % 4 == 0
	&& (puzzle[3][3] + i9 + i8 + i6) % 4 == 0)

一旦条件满足(即可以被调零),说明此时枚举的第一行就是可以让所有时钟调零的情况,但是这种情况并不是唯一,还有其他第一行也许也可以,我们是要寻找步骤最少的方法,所以进入该条件语句后还要进一步判断是否是最短路径。

代码如下(sum是一开始设的一个很大的数)

int temp[9] = { i1,i2,i3,i4,i5,i6,i7,i8,i9 };
if (sum > i1 + i2 + i3 + i4 + i5 + i6 + i7 + i8 + i9)  //不断找到步数最少的
{
	sum = i1 + i2 + i3 + i4 + i5 + i6 + i7 + i8 + i9;
	for (int i = 0; i < 9; ++i)
	    result[i] = temp[i];
	return;
}
else
	return;

至此,本程序的最主要的代码就写好了。

接下来是main函数和枚举函数enumerate()

程序完整代码如下:

#include <iostream>
using namespace std;

int i1, i2, i3, i4, i5, i6, i7, i8, i9;
int puzzle[4][4] = { 0 };
int result[9] = { 0 };
int sum = 10000;

void guess()
{
	i4 = (4 - (puzzle[1][1] + i2 + i1) % 4) % 4;
	// 确定了1行1列元素被置零,同时求出i4
	i5 = (4 - (puzzle[1][2] + i1 + i2 + i3) % 4) % 4;
	// 确定了1行2列元素被置零,同时求出i5
	i6 = (4 - (puzzle[1][3] + i2 + i3) % 4) % 4;
	// 确定了1行3列元素被置零,同时求出i6
	i7 = (4 - (puzzle[2][1] + i4 + i1 + i5) % 4) % 4;
	// 确定了2行1列元素被置零,同时求出i7
	i8 = (4 - (puzzle[3][1] + i7 + i4) % 4) % 4;  
	// 确定了3行1列元素被置零,同时求出i8
	i9 = (4 - (puzzle[2][2] + i5 + i1 + i3 + i7) % 4) % 4;
	// 确定了2行2列元素被置零,同时求出i9
	
	// 到目前为止,还有2行3列、3行2列和3行3列元素没有被置零,我们接下来就判断已知所有元素press的情况下,
	// 这三个元素是否会被置零,如果没有被置零说明第一行的枚举情况不对,返回false
	if ((puzzle[2][3] + i3 + i9 + i5 + i6) % 4 == 0
		&& (puzzle[3][2] + i5 + i7 + i8 + i9) % 4 == 0
		&& (puzzle[3][3] + i9 + i8 + i6) % 4 == 0)
	{
		int temp[9] = { i1,i2,i3,i4,i5,i6,i7,i8,i9 };
		if (sum > i1 + i2 + i3 + i4 + i5 + i6 + i7 + i8 + i9)  //不断找到步数最少的
		{
			sum = i1 + i2 + i3 + i4 + i5 + i6 + i7 + i8 + i9;
			for (int i = 0; i < 9; ++i)
				result[i] = temp[i];
			return;
		}
		else
			return;
	}
	return;
}
void enumerate()
{
	for (i1 = 0; i1 < 4; i1++)
		for (i2 = 0; i2 < 4; i2++)
			for (i3 = 0; i3 < 4; i3++)
				guess();
}
int main()
{
	for (int i = 1; i <= 3; i++)
		for (int j = 1; j <= 3; j++)
			cin >> puzzle[i][j];
	enumerate();
	for (int i = 0; i < 9; i++)
		if (result[i] != 0)
			for (int c = 0; c < result[i]; c++)
				cout << i + 1 << ' ';

	return 0;
}

此题与熄灯问题的区别:

1、此题枚举时必须全部枚举完,熄灯问题是枚举到有正确的就输出,因为只有一种熄灯解,所以是while(guess()==false){}因此本题的guess函数返回类型不为bool型而是为空

2、熄灯问题规律性更强,此题由于只有九个时钟,所以guess()函数里确定拨动次数时直接列了6个运算表达式,而熄灯问题中由于有很多行,所有用for循环挨个往下,但他们的目标都是一样的,即找到在第一行已知的情况下,所有元素的press(在本例中press就是拨动次数)

#------2022年3月28日------#

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
根据提供的引用内容,可以得知这是一道关于迷宫问题的题目,需要使用Java语言进行编写。具体来说,这道题目需要实现一个迷宫的搜索算法,找到从起点到终点的最短路径。可以使用广度优先搜索或者深度优先搜索算法来解决这个问题。 下面是一个使用广度优先搜索算法的Java代码示例: ```java import java.util.*; public class Main { static int[][] maze = new int[5][5]; // 迷宫地图 static int[][] dir = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}}; // 方向数组 static boolean[][] vis = new boolean[5][5]; // 标记数组 static int[][] pre = new int[5][5]; // 记录路径 public static void main(String[] args) { Scanner sc = new Scanner(System.in); for (int i = 0; i < 5; i++) { for (int j = 0; j < 5; j++) { maze[i][j] = sc.nextInt(); } } bfs(0, 0); Stack<Integer> stack = new Stack<>(); int x = 4, y = 4; while (x != 0 || y != 0) { stack.push(x * 5 + y); int t = pre[x][y]; x = t / 5; y = t % 5; } stack.push(0); while (!stack.empty()) { System.out.print(stack.pop() + " "); } } static void bfs(int x, int y) { Queue<Integer> qx = new LinkedList<>(); Queue<Integer> qy = new LinkedList<>(); qx.offer(x); qy.offer(y); vis[x][y] = true; while (!qx.isEmpty()) { int tx = qx.poll(); int ty = qy.poll(); if (tx == 4 && ty == 4) { return; } for (int i = 0; i < 4; i++) { int nx = tx + dir[i][0]; int ny = ty + dir[i][1]; if (nx >= 0 && nx < 5 && ny >= 0 && ny < 5 && maze[nx][ny] == 0 && !vis[nx][ny]) { vis[nx][ny] = true; pre[nx][ny] = tx * 5 + ty; qx.offer(nx); qy.offer(ny); } } } } } ``` 该代码使用了广度优先搜索算法,首先读入迷宫地图,然后从起点开始进行搜索,直到找到终点为止。在搜索的过程中,使用标记数组记录已经访问过的位置,使用路径数组记录路径。最后,使用栈来输出路径。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值