三道POJ中的回溯問題
阿新 • • 發佈:2018-11-11
問題概要
本文是我做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;
}
總結
回溯問題關鍵還是明確選擇範圍,將其與原始的八皇后問題進行聯絡抽象,並且注意一些判斷條件的細節即可。