博客园 首页 私信博主 显示目录 隐藏目录 管理

P1985 翻转棋

题目描述

农夫约翰知道,聪明的奶牛可以产更多的牛奶。他为奶牛设计了一种智力游戏,名叫翻转棋。

翻转棋可以分成 M × N 个格子,每个格子有两种颜色,一面是黑的,一面是白的。

一旦翻转某个格子,这个格子的颜色就会颠倒。如果把所有的格子都翻成白的,就算奶牛赢

了。然而,奶牛的蹄子很大,一旦它们打算翻转某个格子,这个格子附近(即和这个格子有

公共边)的格子也会被翻转。一直翻来翻去也很无聊,奶牛们想最小化必须翻动的次数。

请帮助奶牛确定翻动的最少次数和具体的翻法。如果最小解有多个,则输出在字典序意义下

最小的那个,如果不可能完成任务,则只要输出一行单词:IMPOSSIBLE。

输入输出格式

输入格式:

第 1 行:两个整数:M 和 N

第 2 ~ M+1 行:第 i+1 行从左到右依次描述了棋盘第 i 行的颜色,共 N 个整数。 1 代

表黑色, 0 代表白色。

输出格式:

第 1 ~ M 行:每行 N 个整数,分别表示在各个格子上翻转的次数。

输入输出样例

输入样例#1:
4 4
1 0 0 1
0 1 1 0
0 1 1 0
1 0 0 1
输出样例#1:
0 0 0 0
1 0 0 1
1 0 0 1
0 0 0 0
说明

·1 ≤ M, N ≤ 15


题解

第一次用markdown写题面感觉很玄学x

一开始以为要枚举每一个点是否翻转,复杂度爆炸exm?

然而事实上一旦第一行确定了,整个棋盘的方案就确定了

于是只需要枚举第一行的翻转方案

然后对于每个方案判断是否可行并且计算最小步数就可以了

这里在判断翻转的时候用了一种玄学思路:

判断当前点是否需要翻转的时候,看一下它上面的那个点的当前状态是否合法

因为上边那个点的上、左、右三个方向都已经确定了

唯一可以改变它的状态的方法是翻转它下边的点,即当前点

所以如果上边的点当前状态为黑,翻转当前点,使上边的点为白

显然这样翻的话对于每个方案除了最后一行之外不会再有别的黑格子

所以只要O(nm)扫一遍整个棋盘,判断最后一行是否有黑格子,若有则不合法

结合枚举第一行方案,总复杂度\(O(2^n*nm)\)

以及只在当前次数比已记录的方案次数小的时候更新答案就可以保证字典序最小(好像是废话

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=20,inf=N*N+9;
int n,m,a[N][N],c[N][N],cnt,ans=inf,out[N][N];
bool solve(){
	for(int i=2;i<=n+1;++i)
	for(int j=1;j<=m;++j){
		int f=0;
		if(i-2>=1)f+=c[i-2][j];
		if(j-1>=1)f+=c[i-1][j-1];
		f+=c[i-1][j+1]+c[i-1][j];
		if((f&1)^a[i-1][j]){if(i==n+1)return false;else c[i][j]=1,++cnt;}
	}
	return true;
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i)
	for(int j=1;j<=m;++j)scanf("%d",&a[i][j]);
	for(int i=0;i<=(1<<m)-1;++i){
		memset(c,0,sizeof(c));cnt=0;
		for(int j=1;j<=m;++j){c[1][m-j+1]=(i>>j-1)&1;if(c[1][m-j+1])++cnt;}
		if(solve()){
			if(cnt<ans){
				ans=cnt;
				for(int i=1;i<=n;++i)
				for(int j=1;j<=m;++j)
				out[i][j]=c[i][j];
			}
		}
	}
	if(ans==inf){cout<<"IMPOSSIBLE"<<endl;return 0;}
	for(int i=1;i<=n;++i){
	for(int j=1;j<=m;++j)
	cout<<out[i][j]<<" ";
	cout<<endl;}
	return 0;
}

by:wypx


继续把luogu的题解来水

枚举

我们发现当第一行确定下来之后,要满足合法,只能通过改变下一行的情况来使上一行合法.
也就是说,当前行可以不用合法,但是再下一行进行的操作一定可以使当前行合法。
所以当前位置\((x_i,y)\)需要改变的条件就是\((x_{i-1},y)\)在本次操作前是1
这次操作全部合法就是最后一行改变后全是0;
于是就枚举第一行的改变,剩下的行和列就递推出来。
我枚举的方式是二进制(其实是不太会dfs)当前位置需要改变就是当前所在的二进制数是1
判断当前是否改变,只要看当前所枚举的数x and(1<<位置)就可以了

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstring>
#include<queue>
#include<cmath>
#define ll long long
using namespace std;
const int maxn=20;
inline int read(){
    int an=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while('0'<=ch&&ch<='9'){an=(an<<3)+(an<<1)+ch-'0';ch=getchar();}
    return an*f;
}
int n,m,map[maxn][maxn];
int Kasumi[maxn][maxn];
int remember[maxn][maxn];//输出答案
bool change[maxn][maxn];//当前改变的位置
int tep=2333333;//当前答案的操作次数
inline void kk(){
    for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)Kasumi[i][j]=map[i][j];
    memset(change,0,sizeof change);
}
inline void dfs(int prepare){
    kk();int tot=0;
    for(int i=1;i<=m;i++){
        if(!prepare)continue;
        if(prepare&(1<<i-1)){
        int now=m-i+1;tot++;
        Kasumi[1][now-1]^=1;
        Kasumi[1][now+1]^=1;
        Kasumi[1][now]^=1;
        Kasumi[2][now]^=1;
        change[1][now]=1;
        }
    }
    for(int i=2;i<=n;i++)
    for(int j=1;j<=m;j++){
        if(Kasumi[i-1][j]){
        tot++;
        Kasumi[i][j-1]^=1;
        Kasumi[i][j+1]^=1;
        Kasumi[i][j]^=1;
        Kasumi[i+1][j]^=1;
        change[i][j]=1;
        }
    }
    if(tot<tep){
        for(int i=1;i<=m;i++)if(Kasumi[n][i])return ;//判断最后一行
        tep=tot;
        for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
        remember[i][j]=change[i][j];
    }
}
int main(){
    n=read();m=read();
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)map[i][j]=read();
    for(int i=0;i<=(1<<m)-1;i++){
        dfs(i);
    }
    if(tep==2333333)cout<<"IMPOSSIBLE";
    else{
        for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++)cout<<remember[i][j]<<" ";
        cout<<endl;
        }
    }
    return 0;
}

\(by:s_a_b_e_r\) ←w:自带特效的名字x

posted @ 2017-10-23 10:29  ck666  阅读(305)  评论(0编辑  收藏  举报