1. 程式人生 > >三道POJ中的回溯問題

三道POJ中的回溯問題

問題概要

本文是我做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;
}

總結

回溯問題關鍵還是明確選擇範圍,將其與原始的八皇后問題進行聯絡抽象,並且注意一些判斷條件的細節即可。