P1985 翻转棋

1 题目简介

1.1 题目大纲

在一个 N × M N×M N×M 的棋盘里,每个格子的颜色要不是黑,要不是白。
这时,约翰FJ 叫奶牛们在这种棋盘上进行一种操作:选定任意一个,该格和这个格子相邻的四个格子都会变色。请找出一种办法,使用尽量少的操作,使得棋上的所有格子都为白色。

1.2 题目难度

内容实际难度
思路难度
代码难度
细节难度

2 思路分析

这一题,看起来蛮难的,其实不难。大家的思路也许是——纯粹暴力枚举。当然,这道题的正解就是暴力枚举啦……可,关键是怎样暴力枚举。提供有两种办法,供大家参考。

思路一

最简单又最简朴的思路:就是每一种全部判定一遍啦……首先,得明白为什么每一个方格要不翻转一遍,要不不翻转。因为:若当任意一个已经翻过一遍了,再翻第二遍,显然,第一遍翻过的格子第二遍又被翻过一次,恢复原样,没有意义。所以最多翻一遍。每种格子要不翻一遍,要不不翻。所以我们递归的搜索每一个格子,执行两种情况:翻和不翻。

DFS 搜索

接下来就要搜索啦!
搜索的总思路如下:一般情况下,执行两种情况(如上述);碰到该行边界时,跳到下一行去,继续搜索。那有的同学会问了:既然要最小值,那怎么统计呢?没关系,直到所有的递归搜索执行完毕,也就是递归到第 m + 1 m+1 m+1 行时,我们就可以执行统计。而这个统计也是稍较麻烦的。首先,我们把所有的状态全部统计出来了,其中必定有不是符合标准的状态,这种错误状态,也就是格子里有黑色棋子的 ( 1 1 1) 。我们就要在统计之前,把这种情况的状态结束运行。至于怎么写代码,就简单了。从第 1 1 1 行第 1 1 1 列到第 m m m 行第 n n n 列,持续搜索有没有出现 1 1 1 的,若有,函数 r e t u r n ; return; return; ,否则,继续运行。

void search_dfs(int x,int y){//参数用来表示第x行第y列
	if(y==m+1){//当前节点越界(行)
		search_dfs(x+1,1);//跳至下一行
		return;//别忘记了,不然数组越界就麻烦了
	}
	if(x==n+1){//当前节点越界(列)
		int sum=0;//用来记录当前状态翻转总数
		for(int i=1;i<=n;i++)for(int j=1;j<=m;j++){
			if(a[i][j])return;//a数组储存当前状态
			if(b[i][j])sum++;//b数组记录每个节点翻转数
		}
		h=1;//h保存是否存在可行情况
		if(sum<minx){//当当前值小于最小值
			minx=sum;//保存
			for(int i=1;i<=n;i++)for(int j=1;j<=m;j++){
				c[i][j]=b[i][j];//保存最优状态
			}
		}
		return;
	}
	search_dfs(x,y+1);//字典序,先0后1
	for(int i=0;i<=4;i++){//翻转后,a的状态要改变
		a[x+d[i]][y+e[i]]=1-a[x+d[i]][y+e[i]];//当前、前后左右
	}
	b[x][y]=1;//当前节点已翻转过
	search_dfs(x,y+1);
	//下面均为回溯流程
	for(int i=0;i<=4;i++){
		a[x+d[i]][y+e[i]]=1-a[x+d[i]][y+e[i]];
	}
	b[x][y]=0;
}

b u t but but,实测 47 47 47 ,TLE 6 6 6 个数据点。还是要优化滴~

思路二

上一个思路TLE了……
那怎么优化呢?这个可是个难点。但是,可以想到如果第一行的状态确定下来了,第二行也就确定了。为什么呢?其实这很好想的。因为以第 1 1 1 行第 1 1 1 列为例,若当前状态为 1 1 1 ,第 2 2 2 行第 1 1 1 列必须是 1 1 1 ,这样才能满足 1 − 1 = 0 1-1=0 11=0 。简单吧!

DFS 搜索

DFS 搜索和前面没什么区别啦……就是确定第一行就行了,所以只带一个参数

void search_dfs(int x){
	if(x==m+1){//数组越界
		dp_xxpanduan(2,1);//跳到判断即可
		return;
	}
	search_dfs(x+1);
	for(int i=0;i<=4;i++){
		a[1+d[i]][x+e[i]]=1-a[1+d[i]][x+e[i]];
	}
	b[1][x]=1;
	search_dfs(x+1);
	for(int i=0;i<=4;i++){
		a[1+d[i]][x+e[i]]=1-a[1+d[i]][x+e[i]];
	}
	b[1][x]=0;
}

判断

难在判断啊!其实也不难。根据前面的推导,我们可以推出如下递推式: d p i , j = 1 − d p i − 1 , j dp_{i,j}=1-dp_{i-1,j} dpi,j=1dpi1,j。然后,推出第 2 2 2 行至第 n n n 行。但是推完后,我们还不能确定当前状态是否是一个正确的状态,所以还要加以判断啦!其实思路二大部分代码和思路一差不多。

void dp_xxpanduan(int x,int y){
	if(y==m+1){
		dp_xxpanduan(x+1,1);
		return;
	}
	if(x==n+1){
		int sum=0;
		for(int i=1;i<=n;i++)for(int j=1;j<=m;j++){
			if(a[i][j])return;
			if(b[i][j])sum++;
		}
		h=1;
		if(sum<minx){
			minx=sum;
			for(int i=1;i<=n;i++)for(int j=1;j<=m;j++){
				c[i][j]=b[i][j];
			}
		}
		return;
	}
	if(!a[x-1][y]){//先0后1
		dp_xxpanduan(x,y+1);
	}else{
		for(int i=0;i<=4;i++){
			a[x+d[i]][y+e[i]]=1-a[x+d[i]][y+e[i]];
		}
		b[x][y]=1;
		dp_xxpanduan(x,y+1);
		for(int i=0;i<=4;i++){
			a[x+d[i]][y+e[i]]=1-a[x+d[i]][y+e[i]];
		}
		b[x][y]=0;
	}
}

AC代码牛逼!!!

3 AC代码

#include<iostream>
#include<cstdio>
using namespace std;
int n,m,a[20][20],b[20][20],c[20][20],d[5]={0,0,1,0,-1},e[5]={0,1,0,-1,0},minx,h;
void dp_xxpanduan(int x,int y){
	if(y==m+1){
		dp_xxpanduan(x+1,1);
		return;
	}
	if(x==n+1){
		int sum=0;
		for(int i=1;i<=n;i++)for(int j=1;j<=m;j++){
			if(a[i][j])return;
			if(b[i][j])sum++;
		}
		h=1;
		if(sum<minx){
			minx=sum;
			for(int i=1;i<=n;i++)for(int j=1;j<=m;j++){
				c[i][j]=b[i][j];
			}
		}
		return;
	}
	if(!a[x-1][y]){
		dp_xxpanduan(x,y+1);
	}else{
		for(int i=0;i<=4;i++){
			a[x+d[i]][y+e[i]]=1-a[x+d[i]][y+e[i]];
		}
		b[x][y]=1;
		dp_xxpanduan(x,y+1);
		for(int i=0;i<=4;i++){
			a[x+d[i]][y+e[i]]=1-a[x+d[i]][y+e[i]];
		}
		b[x][y]=0;
	}
}
void search_dfs(int x){
	if(x==m+1){
		dp_xxpanduan(2,1);
		return;
	}
	search_dfs(x+1);
	for(int i=0;i<=4;i++){
		a[1+d[i]][x+e[i]]=1-a[1+d[i]][x+e[i]];
	}
	b[1][x]=1;
	search_dfs(x+1);
	for(int i=0;i<=4;i++){
		a[1+d[i]][x+e[i]]=1-a[1+d[i]][x+e[i]];
	}
	b[1][x]=0;
}
int main(){
	h=0;
	minx=400;
	cin>>n>>m;
	for(int i=1;i<=n;i++)for(int j=1;j<=m;j++){
		scanf("%d",&a[i][j]);
	}
	search_dfs(1);
	if(h){
		for(int i=1;i<=n;i++){
			for(int j=1;j<=m;j++){
				printf("%d ",c[i][j]);
			}
			printf("\n");
		}
	}else{
		if(minx==400)printf("IMPOSSIBLE");
		else{for(int i=1;i<=n;i++){
			for(int j=1;j<=m;j++){
				printf("%d ",0);
			}
			printf("\n");
		}
		}
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值