UVA - 519 Puzzle (II)(回溯+剪枝)

本文介绍了一个拼图游戏问题,玩家需要将15块带有不同边类型的拼图块组成一个矩形。文章详细解释了游戏规则,并提供了一种通过深度优先搜索(DFS)结合剪枝策略的解决方案。


  Puzzle (II) 

Little Barborka has just started to learn how to solve a picture puzzle. She has started with a small one containing 15 pieces. Her daddy tries to solve the puzzle too. To make it a little bit harder for himself, he has turned all puzzle pieces upside down so that he cannot see pictures on the pieces. Now he is looking for a solution of the puzzle. Normally the solution should exist but he is not sure whether Barborka has not replaced some pieces of the puzzle by pieces of another similar puzzle. Help him and write a program which reads a description of a set of puzzle pieces and decides whether it is possible to assembly the pieces into a rectangle with given side lengths or not.

Input 

The input file consists of blocks of lines. Each block except the last describes one puzzle problem. In the first line of the block there are integers  nand  m$0 < n, m �\le 6$ separated by one space. The integers  nm indicate the number of rows and columns in the puzzle respectively. The description of individual puzzle pieces is in the following  $n \times m$ lines of the block. Each piece is a rectangle 3 centimeters wide and 4 centimeters high with possible juts or cavities in the middle of its sides. For each side of a puzzle piece just one of the following possibilities is true (see picture):

  • there is no jut or cavity on the side, i.e., the side is flat - such sides can be used only on edges of the final picture when assembling the puzzle,
  • there is one jut in the middle of the side,
  • there is one cavity in the middle of the side.


\begin{picture}(2301,2118)(0,-10)\put(585.000,912.000){\arc{450.000}{1.5708}{4.......{\SetFigFont{14}{16.8}{\rmdefault}{\mddefault}{\updefault}jut}}}}}\end{picture}

As is usual, two pieces can be placed side by side only if one has a jut and the other has a cavity on corresponding sides. We will denote the flat sides by F, the sides with juts by O and the sides with cavities by I. Each piece is described by four letters characterizing its top, right, bottom, and left side. To make the task easier the pieces can be used only as they are described i.e. they cannot be turned.

After each block there is an empty line. The last block consists of just one line containing 0 0, i.e. two zeros separated by one space.

Output 

The output file contains the lines corresponding to the blocks in the input file. A line contains  YES if the corresponding block in the input file describes a puzzle that can be correctly assembled. Otherwise it contains  NO. There is no line in the output file corresponding to the last ``null'' block of the input file.

Sample Input 

3 5
FOOF
FOOI
FOOI
FOOI
FFOI
IOOF
IOOI
IOOI
IOOI
IFOI
IOFF
IOFI
IOFI
IOFI
IFFI
0 0

Sample Output 

YES


题意:
输入n,m。表示有一个n*m的拼图。然后输入每一块拼图F代表平的。I代表凸的。O代表凹的。要求判断这个拼图能不能拼出来。
字符串中的每个位置的字符从左到右,分别表示:顶、右、下、左。

思路:dfs+剪枝。一格格去放拼图。放不了就进行回溯。

剪枝点:
1、由于边缘一定是平的,内部一定是凸凹结合的。所以判断F的数量等不等于2 * (n + m),以及I的数量等不等于O的数量。如果都等于再进行dfs。
2、如果有一块拼图在一个位置上不能拼。那么下次遇到一样的拼图的时候直接跳过。

注意:衔接处的判断。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

const int N = 8;
int tot;
int m,n;
int vis[N*N];
char piece[N*N][5];
char grid[N][N][5];
int in,out,flat;
bool ok;

int cmp(const void *a,const void *b) {
	return strcmp((char*)a,(char*)b);
}

void init() {
	memset(grid,0,sizeof(grid));
	memset(vis,0,sizeof(vis));
	in = out = flat = 0;
}

bool judge(int cur,int r,int c) {
	if(r == 0 && piece[cur][0] != 'F' ||
			r == m-1 && piece[cur][2] != 'F' ||
			c == 0 && piece[cur][3] != 'F' ||
			c == n-1 && piece[cur][1] != 'F') {
		return false;
	}
	if(r > 0) {
		if(piece[cur][0] == 'I' && grid[r-1][c][2] != 'O' || 
				piece[cur][0] == 'O' && grid[r-1][c][2] != 'I' ||
				piece[cur][0] == 'F' && grid[r-1][c][2] != 'F') {
			return false;
		}
	}
	if(c > 0) {
		if(piece[cur][3] == 'I' && grid[r][c-1][1] != 'O' || 
				piece[cur][3] == 'O' && grid[r][c-1][1] != 'I' ||
				piece[cur][3] == 'F' && grid[r][c-1][1] != 'F') {
			return false;
		}
	}
	return true;
}

void dfs(int r,int c) {
	if(r == m) {
		ok = true;
		return;
	}
	if(c == n) {
		dfs(r+1,0);
		return;
	}
	char tmp[5] = "a";
	for(int i = 0; i < tot; i++) {
		if(!vis[i] && judge(i,r,c) && strcmp(tmp,piece[i])) {
			strcpy(tmp,piece[i]);
			strcpy(grid[r][c],piece[i]);
			vis[i] = true;
			dfs(r,c+1);
			vis[i] = false;
			if(ok) {
				return;
			}
		}
	}
}

int main() {
	while(scanf("%d%d",&m,&n) != EOF && (m || n)) {
		init();
		tot = m*n;
		for(int i = 0; i < tot; i++) {
			scanf("%s",piece[i]);
			for(int j = 0; j < 4; j++) {
				if(piece[i][j] == 'I') {
					in++;
				}else if(piece[i][j] == 'O') {
					out++;
				}else if(piece[i][j] == 'F') {
					flat++;
				}
			}
		}
		qsort(piece,tot,sizeof(piece[0]),cmp);
		ok = false;
		if(flat == 2*(m+n) && in == out) {
			dfs(0,0);
		}
		if(ok) {
			printf("YES\n");
		}else {
			printf("NO\n");
		}
	}
	return 0;
}

### 使用回溯算法求解8-Puzzle问题 #### 方法概述 八数码谜题(8-puzzle),也称为九宫格拼图,是一个典型的组合搜索问题。该问题的目标是从初始状态到达目标状态,其中只有一个空白位置允许相邻的瓷砖移动。为了利用回溯法解决此问题,需要定义一个递归函数来探索所有可能的状态转换路径直到找到解决方案。 在每次迭代过程中,程序会尝试将当前空位与其四个方向上的任意一块合法邻近砖块交换位置,并记录下新的棋盘布局作为下一步操作的基础。如果某一步骤达到了预期的结果,则返回成功;否则继续深入未访问过的节点进行试探直至穷尽一切可能性或发现解答为止[^1]。 #### 实现细节 以下是Python实现的一个简单版本: ```python from copy import deepcopy def is_goal(state, goal_state): """判断是否达到目标""" return state == goal_state def find_blank_position(puzzle): """查找空白的位置""" for i in range(len(puzzle)): try: j = puzzle[i].index(0) return (i,j) except ValueError: continue raise Exception('No blank position found') moves = [(0,-1), (-1,0), (0,+1), (+1,0)] # 左 上 右 下 移动向量 def move_tile(puzzle, direction): """按照指定的方向移动空白处旁边的瓦片""" new_puzzle = deepcopy(puzzle) row,col=find_blank_position(new_puzzle) drow,dcol=direction target_row,target_col=row+drow,col+dcol if not ((0<=target_row<len(new_puzzle)) and (0<=target_col<len(new_puzzle))): return None temp=new_puzzle[target_row][target_col] new_puzzle[row][col]=temp new_puzzle[target_row][target_col]=0 return new_puzzle def backtrack_search(initial_state, goal_state): def _backtrack(current_path): current_board=current_path[-1] if is_goal(current_board,goal_state): print("Solution Found!") for step in current_path: display(step) return True possible_moves=[move for move in moves \ if move_tile(current_board,move)] visited_states=set([str(x)for x in current_path]) unvisited_neighbors=[state for state in possible_moves\ if str(state)not in visited_states] for neighbor in unvisited_neighbors: next_path=current_path+[neighbor] result=_backtrack(next_path) if result==True:return True return False start=[[initial_state]] solution_found=_backtrack(start) if not solution_found: print("No Solution Exists.") ``` 这段代码展示了如何使用回溯方法寻找从给定起始配置到结束配置的有效序列。注意这里简化了一些实际应用中的考虑因素,比如剪枝策略和启发式评估等,这些都可以进一步提高效率[^5]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值