版权声明:欢迎转载,请注出处 https://blog.csdn.net/qq_33850304/article/details/82958430
问题概要
本文是我做POJ-1753&2965&1321三道题目的感想,这个搜索不是单点出发的,而且在搜索过程中有回溯问题,在此总结一下。
题目描述
POJ-1753是翻棋子游戏,翻一个棋子,周围的棋子(上下左右)也会变色,要求翻成全部一个颜色。
POJ-2965与之类似,是开启冰箱门,按下一个开关,同行同列的开关都会翻转状态,要求全部变为开状态。
POJ-1321是一个八皇后的变种,棋盘上指定的位置可以放棋子,棋子的个数也不一定和棋盘行数一样。
题目解析
trick
变量能用全局就用全局,可以不必通过函数的参数来传递了,使得函数的参数更少,看起来更简洁
- 八皇后求解的个数
这是最经典的回溯问题了,注意重点是1.终点判断2.明确下一步选择范围3.做出尝试后递归进入下一层4.状态回退。
这里终点自然是八行棋盘上均放上了棋子;下一步选择可以遍历所有八个空格,根据冲突原则进行筛选;然后将选择数组(solution)的第i项赋上值,进入下一行的判断;将赋值清零,实现回退。
#include <iostream>
using namespace std;
int solution[8] = {0};
int ans=0;
bool check(int row, int chess){
for (int i = 0; i < row; ++i)
{
if (solution[i] == chess)
return false;
if (solution[i]-chess == i-row || solution[i]+i == chess+row)
return false;
}
return true;
}
void backtrace(int row){
if (row == 8){
ans++;
return;
}
for (int i = 1; i <= 8; ++i)
{
solution[row] = i;
if (check(row, i))
backtrace(row+1);
solution[row] = 0;
}
}
int main(){
backtrace(0);
cout<<ans<<endl;
getchar();
return 0;
}
- POJ-1753
这道题目中选择第二个重点–即明确下一步选择的范围–显得不是很明确,而且并不一定要翻第几个棋子,但可以确定的是最多翻16次就够了。
这样倒是变成了一个16行X1列的棋盘,每一行都可以放也可以不放棋子,没有冲突限制,所以每一行的两个选择都尝试一下即可。
当然注意放了棋子(即翻转此棋子即周边)的话要状态回退。
#include <iostream>
using namespace std;
int board[4][4] = {0};
int ilist[] = {1,-1,0,0,0};
int jlist[] = {0,0,-1,1,0};
int ans = 0xff;
void flip(int i, int j){
for (int k=0; k<5; k++){
if (i+ilist[k]>=0 && i+ilist[k]<4 && j+jlist[k]>=0 && j+jlist[k]<4){
board[i+ilist[k]][j+jlist[k]] ^= 1;
}
}
}
bool result(){
int sum=0;
for (int i = 0; i < 4; ++i){
for (int j = 0; j < 4; ++j){
sum += board[i][j];}}
if (sum==0 || sum==16)
return true;
return false;
}
void dfs(int i, int j, int deep){
int nj, ni;
if (result()){
if (deep<ans){
ans = deep;
}
return;
}else if (i>=4){
return;
}
ni = i+((j+1)/4);
nj = (j+1)%4;
dfs(ni, nj, deep);
flip(i, j);
dfs(ni, nj, deep+1);
flip(i, j);
return;
}
int main()
{
int i, j;
for (i = 0; i < 4; ++i)
{
for (j = 0; j < 4; ++j)
{
board[i][j] = (getchar()=='b')?0:1;
}
getchar();
}
dfs(0,0,0);
if (ans == 0xff)
cout<<"Impossible"<<endl;
else
cout<<ans<<endl;
return 0;
}
- POJ-2965
也是每一行都有两个选择,但是注意因为要输出按钮顺序,所以注意用tmp变量来存储每次操作,若到达终点条件则将之赋值给solution,最后方便输出。
#include <iostream>
#include <stdio.h>
using namespace std;
int ans=0xfff;
int board[4][4] = {0};
int solution[20][2] = {0};
int tmp[20][2] = {0};
bool check(){
int sum = 0;
for (int i = 0; i < 4; ++i)
{
for (int j = 0; j < 4; ++j)
{
sum += board[i][j];
}
}
return sum==16;
}
void filp(int x, int y){
for (int i = 0; i < 4; ++i)
{
board[x][i] ^= 1;
board[i][y] ^= 1;
}
board[x][y] ^= 1;
}
void dfs(int i, int j, int step){
int ni, nj;
if (check()){
if (ans > step){
ans = step;
memcpy(solution, tmp, sizeof(tmp));
}
return;
}
if (i == 4) return;
nj = (j+1)%4;
ni = i+(j+1)/4;
dfs(ni, nj, step);
filp(i ,j);
tmp[step][0] = i+1;
tmp[step][1] = j+1;
dfs(ni, nj, step+1);
filp(i, j);
}
int main(int argc, char const *argv[])
{
for (int i=0; i<4; ++i)
{
for(int j=0; j<4; j++){
board[i][j] = (getchar()=='-')?1:0;
}
getchar();
}
dfs(0,0,0);
cout<<ans<<endl;
for (int i = 0; i < ans; ++i)
{
cout<<solution[i][0]<<" "<<solution[i][1]<<endl;
}
return 0;
}
- POJ-1321
简化版八皇后问题,对限制条件更少,只要此处可以放置棋子,并且此位置(此列)之前没有棋子放置过,即可在此放置棋子并进入下一层继续探索。
#include <iostream>
using namespace std;
int count=0;
int n,k;
int board[8][8]={0};
int solution[8]={0};
void backtrace(int row, int step){
if (step == k) {
++count;return;
}
if (row == n) return;
backtrace(row+1, step);
for (int i = 0; i < n; ++i)
{
if (board[row][i] && !solution[i]){
solution[i] = 1;
backtrace(row+1, step+1);
solution[i] = 0;
}
}
}
int main(int argc, char const *argv[])
{
int c;
int i,j;
while(cin>>n>>k&&n!=-1&&k!=-1){
count = 0;
getchar();
for (i=0; i<n; i++){
for (j=0; j<n; j++){
board[i][j] = (getchar()=='#')?1:0;
}
getchar();
}
backtrace(0, 0);
cout<<count<<endl;
}
return 0;
}
总结
回溯问题关键还是明确选择范围,将其与原始的八皇后问题进行联系抽象,并且注意一些判断条件的细节即可。