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
1−1=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=1−dpi−1,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;
}