枚舉,對於狀態或者搜索空間很少的情況可以用,是一種笨重的聰明方法!!
但有時候可以更深入地挖掘題目,得到更好的算法,例如poj2965
1.poj1753 flip game
題目:一个4*4的棋盤放滿棋子,每個棋子不是白色就是黑色。每次选择任意一个棋子翻轉变成相反的颜色,同時也要翻轉它相鄰的上,下,左,右棋子(如果存在的话)。问要把棋盤变成同一个颜色,最少需执行几次上面的操作。
分析:每一個格子至多翻轉1次。由於是4*4=16,只有2.^16=4096個狀態,最多也就16次操作。枚舉即可,“最少”的要求就用DFS來實現。所以本體既是枚舉也是DFS。
#include "stdafx.h"
#include<iostream> //240K 360MS
using namespace std;
bool map[6][6], find = false;
int step; //步數
int dr[5] = {-1, 0, 0, 0, 1}; //行的上、左、中、右、下
int dc[5] = {0, -1, 0, 1, 0};
bool isgoal(){ // 判断矩阵是否为同一个颜色。
for(int i = 1; i <= 4; i ++)
for(int j = 1; j <= 4; j ++)
if(map[i][j] != map[1][1])
return false;
return true;
}
void flip(int row, int col){ // 翻动点(row,col)时的map[][]的变化。
for(int i = 0; i < 5; i ++){
int r = row + dr[i], c = col + dc[i];
map[r][c] = !map[r][c];
}
}
void dfs(int row, int col, int dep){ // 点(row,col)为现在是否要操作的点。
if(dep == step){
find = isgoal();
return;
}
if(find || row == 5) return;
flip(row, col); // 对点(row,col)进行翻动。
if(col < 4) dfs(row, col + 1, dep + 1); //翻,順序搜索
else dfs(row + 1, 1, dep + 1);
flip(row, col); // 還原,等價于不对点(row,col)进行翻动。
if(col < 4) dfs(row, col + 1, dep); //不翻,順序搜索
else dfs(row + 1, 1, dep);
}
int _tmain(int argc, _TCHAR* argv[])
{
char c;
for(int i = 1; i <= 4; i++)
for(int j = 1; j <= 4; j++){
cin >> c;
if(c == 'b') map[i][j] = true;
}
for(step = 0; step <= 16; step ++){ // 枚举 16 步。
dfs(1, 1, 0);
if(find) break;
}
if(find) cout << step << endl;
else cout << "Impossible" << endl;
return 0;
}
2. poj2965 the Pilots Brothers'refrigerator
題目:冷藏庫有16個handle,每個handle只有“open”和“closed”這兩種狀態,當且僅當16個handle都open時冷藏庫的門才開得了。這十六個handle以4*4的矩陣給出,“-”表open,而“+”表closed,且每個局面至少有一個closed的handle。每次對一個位置的handle改變狀態的時候也會該表同行同列的handle的狀態。給定一個局面,求最小的改變次數使得門開,且輸出改變了哪些handle.
分析:和poj1753一樣,枚舉,不過這次需要記錄翻的位置,在哪裡記錄才是問題,肯定是在找到更小翻轉次數的時候進行記錄。
#include <iostream> //240K 297MS
using namespace std;
bool s[4][4],a[16],b[16]; //s記錄局面,a記錄狀態改變,b記錄需要翻的位置
int t=17; //17是最大的翻轉次數的界
void dfs(int p,int c) //c是當前翻的次數
{
int i,j,k=1,x,y;
for(i=0;k&&i<4;i++)
for(j=0;k&&j<4;j++)
if(s[i][j]!=0)k=0;
if(k){
if(c<t){ //找到更小的翻轉次數
t=c;
for(i=0;i<16;i++)b[i]=a[i]; //記錄
}
return;
}
if(p>15)return;
dfs(p+1,c); //不翻,繼續搜索
x=p/4; y=p%4;
for(i=0;i<4;i++){ //翻
s[i][y]=!s[i][y]; s[x][i]=!s[x][i];
}
s[x][y]=!s[x][y];
a[p]=1;
dfs(p+1,c+1); //翻,然後繼續搜索
for(i=0;i<4;i++){ //還原
s[i][y]=!s[i][y]; s[x][i]=!s[x][i];
}
s[x][y]=!s[x][y];
a[p]=0;
}
int _tmain(int argc, _TCHAR* argv[])
{
char temp;
for(int i=0;i<4;i++){
for(int j=0;j<4;j++){
cin>>temp;
if(temp=='+')s[i][j]=1;
else s[i][j]=0;
}
}
memset(a,0,sizeof(a));
dfs(0,0);
cout<<t<<endl;
for(int i=0;i<16;i++)
if(b[i]) cout<<i/4+1<<' '<<i%4+1<<endl;
return 0;
}
進一步分析:
和poj1753不同,本題沒有“impossible”的局面,是基於這樣的觀察:要是得一個位置的handle改變狀態而其他handle都不變,只需將該位置和同行同列的handle都翻轉一下!比如對handle[i][j]而言,如上操作之後,位置(i, j)被改變了7次,而同行同列的位置則被改變了4次,而其他的位置則被改變了2次。那么在輸入的時候,遇到“+”的時候就把該位置自加一,且同行同列的其他位置也自加一,等輸入完成后遍歷handle矩陣,奇數位置的就是要改變的!!這一點可以擺個棋盤從簡單的一個“+”入手研究得出。
#include "stdafx.h"
#include <iostream> //240K 47MS
using namespace std;
bool handle[4][4];
int _tmain(int argc, _TCHAR* argv[])
{
char temp;
for(int i=0;i<4;i++){
for(int j=0;j<4;j++){
cin>>temp;
if(temp=='+'){
for(int row=0;row<4;row++){ //同列的自加一
if(row==i) continue;
else handle[row][j]=!handle[row][j];
}
for(int col=0;col<4;col++){ //同行的自加一
if(col==j) continue;
else handle[i][col]=!handle[i][col];
}
handle[i][j]=!handle[i][j]; //自己自加一
}
}
}
int sum=0;
for(int i=0;i<4;i++){ //計算奇數狀態的個數
for(int j=0;j<4;j++){
if(handle[i][j])
sum++;
}
}
cout<<sum<<endl;
for(int i=0;i<4;i++){
for(int j=0;j<4;j++){
if(handle[i][j])
cout<<i+1<<' '<<j+1<<endl;
}
}
return 0;
}