DFS.枚舉::poj1753 flip game && poj2965 the Pilots Brothers'refrigerator

 枚舉,對於狀態或者搜索空間很少的情況可以用,是一種笨重的聰明方法!!

但有時候可以更深入地挖掘題目,得到更好的算法,例如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;
}


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值