三道POJ中的回溯问题

版权声明:欢迎转载,请注出处 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;
}

总结

回溯问题关键还是明确选择范围,将其与原始的八皇后问题进行联系抽象,并且注意一些判断条件的细节即可。

猜你喜欢

转载自blog.csdn.net/qq_33850304/article/details/82958430
今日推荐