POJ3279(开关后续)

描述:
一个\(n*m的矩阵,每个格子有0和1两种状态.每次可以翻一个格子,并且此格子的上下左右都要被翻。\)
\(目标状态应该全为0,求最少翻的次数,输出最小字典序的方案\)

这儿可就麻烦了啊,开关从一维变到了二维,不能通过确定左上角的状态往后递推

但是,我们可以枚举第一行的状态,第二行怎么翻就确定了,因为此时上面的格子只有下面的格子可以改变

Ⅰ.枚举和预处理

关于枚举第一行,可以用二进制数很方便的表示出来

同样f[i][j]表示(i,j)位置有没有被翻过,那我们统计一下上下左右被翻的次数就可以的得知此时状态

最后,判断第N行是否全为0即可。

#include <cstring>
#include <iostream>
using namespace std;
const int inf=1<<29;
int n,m,ans,a[20][20],f[20][20],s[20][20];
int b[5]={0,0,1,-1},c[5]={1,-1,0,0};
bool isfilp(int x,int y)
{
	int k=f[x][y]+a[x][y];//统计被翻过的次数 
	for(int i=0;i<4;i++)
	{
		int nx=x+b[i],ny=y+c[i];
		if(nx<1||ny<1||nx>n||ny>m)	continue;
		k+=f[nx][ny];
	}
	return k%2; 
}
int cal()
{
	int ans=0;
	for(int i=2;i<=n;i++)
	for(int j=1;j<=m;j++)
		if(isfilp(i-1,j))//如果需要翻转
		f[i][j]=1;
	for(int i=1;i<=m;i++)	
	if(isfilp(n,i))	return -1;//最后一行还要被翻,显然不可行 
	for(int i=1;i<=n;i++)
	for(int j=1;j<=m;j++)
	ans+=f[i][j];
	return ans;
}
int main()
{
	while(cin>>n>>m)
	{
		ans=inf;
		for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
		cin>>a[i][j];
		int last=1<<m;
		for(int i=0;i<last;i++)
		{
			memset(f,0,sizeof(f));
			for(int j=1;j<=m;j++)
				if(i&(1<<(j-1)))
					f[1][j]=1;//初始化第1行
			int val=cal();
			if(val==-1)	continue;
			if(val<ans) 
			{
				ans=val;
				memcpy(s,f,sizeof(f));//把方案保存下来 
			}
		}
		if(ans==inf)	cout<<"IMPOSSIBLE"<<endl;
		else
		{
			for(int i=1;i<=n;i++)
			{
				for(int j=1;j<=m;j++)
					cout<<s[i][j]<<" ";
				cout<<endl;
			}
		}
	}
	return 0;
}
posted @ 2020-03-31 16:48  倾叶子佮  阅读(117)  评论(0编辑  收藏  举报