练习枚举方法,网上推荐做POJ1753,感觉似曾相识,不管,就做了,后来才发现,这道题目,过去做过,不过过去用的是DFS方法,其实两种方法都不错,都属于基本算法,而且模拟操作也适合基础练习。

模拟的话应该应该建立两个矩阵initMap[][],ope[][], (因为每次枚举都要初始化棋盘所以,每次拷贝棋盘到map[][]上,在map[][]上进行操作修改。)

假设map[i][j]=1则为黑色,map[i][j]=0为白色

每次枚举ope[][]矩阵的第一行ope[1][j],共有16中情况{0,0,0,0},{0,0,01}.....{1,1,1,1}

可以发现一个规律,如果从上到下,从左到右填写ope[][]那么,第2,3,4行的ope[][]取值一定要保证此位置的正上方为欲达到的颜色,用公式表达为(假设想全部为黑色)

如果map[i-1][j]==0 ,则ope[i][j]为1 否则为0;

而该行的map修改为map[j][k]=(map[j][k]+ope[j][k]+ope[j][k-1]+ope[j][k+1]+ope[j-1][k])%2;

在枚举第一行的时候,下面代码实现的不是很好,如果改用位操作更加合适,这个技巧很不错,比如

for(int i=0;i<16;i++){

  for(int j=0;j<4;j++){

    if( i & (1<<j) ) 

      ope[1][j]=1;

    else

      ope[1][j]=0;

}  

具体代码如下:

#include<iostream>
using namespace std;
int map[6][6];
int initMap[6][6];
int ope[6][6];
void opeInit(int i){//初始化操作矩阵第一行
int temp=i;
for(int j=1;j<=4;j++){
ope[1][j]=temp%2;
temp/=2;
}
for(int j=1;j<=4;j++)
map[1][j]=(map[1][j]+ope[1][j]+ope[1][j-1]+ope[1][j+1])%2;
return ;
}

int calOpe(int i,int black){
//拷贝棋盘,初始化
for(int k=1;k<=4;k++)
for(int j=1;j<=4;j++)
map[k][j]=initMap[k][j];

memset(ope,0,sizeof(ope));
opeInit(i);//第一行采取枚举,后几行根据上一行棋盘情况

for(int j=2;j<=4;j++){
//完成对ope矩阵的填充
for(int k=1;k<=4;k++){
if(map[j-1][k]==black) //
ope[j][k]=1;
else
ope[j][k]=0;
}
//修改当前棋盘的状态
for(int k=1;k<=4;k++){
map[j][k]=(map[j][k]+ope[j][k]+ope[j][k-1]+ope[j][k+1]+ope[j-1][k])%2;
}
}

//检验是否符合要求
bool isOk=true;
for(int i=1;i<=4;i++){
if(map[4][i]==black){//
isOk=false;
break;
}
}
int sum=0;
if(isOk){
for(int i=1;i<=4;i++)
for(int k=1;k<=4;k++){
if(ope[i][k]==1)
sum++;
}
return sum;
}
return 20;
}
int smaller(int a,int b){
if(a<b)return a;
else return b;
}


int main(){
char cinchar;
int min=20;
//棋盘输入操作
for(int i=1;i<=4;i++)
for(int j=1;j<=4;j++){
cin>>cinchar;
if(cinchar=='b')initMap[i][j]=1;
else initMap[i][j]=0;
}
for(int i=0;i<=15;i++){//枚举16次选择最大值
int blackNum=calOpe(i,1);
int whiteNum=calOpe(i,0);
int smallest=smaller(blackNum,whiteNum);
if(min>smallest)min=smallest;
}
if(min==20)cout<<"Impossible"<<endl;
else cout<<min<<endl;
return 0;
}




 

DFS实现方法如下;

//如果能实现的话,最多用16步(证明用鸽笼原理很容易理解)
//那么就从第0步开始查找,如果查找到符合的情况,修改f,停止查找
//即找到最小的step

//代码最为关键的部分是dfs():解释见代码部分

**************************************************************/
#include<iostream>
usingnamespace std;

bool map[6][6],f=0;
int dir[5][2]={{0,0},{0,1},{1,0},{-1,0},{0,-1}};
int step;
//翻转操作
void roll(int row,int col){
for(int i=0;i<5;i++){
int x=row+dir[i][0];
int y=col+dir[i][1];
map[x][y]=!map[x][y];
}
}
//判断
bool judge(){
int i,j;
for(i=1;i<=4;i++)
for(j=1;j<=4;j++)
if(map[i][j]!=map[1][1])
return0;
return1;
}

/*************************************************************/

bool dfs(int row,int col ,int dep){
if(dep==step){
f = judge();
return0;
}//由后面可以发现dfs的走向是从左到右,从上到下的
//所以当row==5的时候也已经实现啦
if(f||row==5)return1;
//翻转一次
roll(row,col);
if(col<4)//当dfs到最右端的时候(即col==4),需要
//控制dfs的走向转为第二行第一个
dfs(row,col+1,dep+1);
else dfs(row+1,1,dep+1);
//再原地翻转即还原
//一定要理解,dep不变与dep+1的区别
roll(row,col);
if(col<4)
dfs(row,col+1,dep);
else dfs(row+1,1,dep);

return0;
}
int main(){
int i,j;
char c;
for(i=1;i<=4;i++){
for(j=1;j<=4;j++){
cin>>c;
if(c=='b')map[i][j]=1;
else map[i][j]=0;
}
}
for(step=0;step<=16;step++){
dfs(1,1,0);
if(f)break;
}
if(f)cout<<step<<endl;
else cout<<"Impossible"<<endl;
return0;
}

posted on 2011-10-12 17:41  geeker  阅读(664)  评论(0编辑  收藏  举报