poj-3279 poj-1753(二进制枚举)

时间:2022-02-19 18:50:39

题目链接:http://poj.org/problem?id=3279

题目大意:

  有一个m*n的棋盘(1 ≤ M ≤ 15; 1 ≤ N ≤ 15),每个格子有两面分别是0或1,每次可以对一个格子做一次翻转操作,将被操作的格子和与其相邻的周围4个格子都会进行翻转。问做少做多少次翻转可以将所有格子翻转成0,输出翻转方案(每个棋子的翻转次数)。没有方案时输出“IMPOSSIBLE”。

Sample Input

4 4
1 0 0 1
0 1 1 0
0 1 1 0
1 0 0 1

Sample Output

0 0 0 0
1 0 0 1
1 0 0 1
0 0 0 0

解题思路:

  首先需要明确每个格子只有两种情况,就是要么翻和要么不翻,因为翻奇数次都与翻1次,而翻偶数次等于没翻。再来看一下数据范围n和m最大都为15,范围不是很大,但是全部枚举的话应该也会超时,所以我们要找到一个方案减少枚举量,我们发现如果一行的状态已经确定了,那么它接下来的是不是都确定了。下一行都要保证上一行全为0就可以了,最后只需要判断一下最后一行是否都为0就可以了,而第一行有n个棋子,它的状态也就是有2^n种。我们可以用一个含有16位的二进制数来表示它的状态,如果第i位为1表示该格子需要翻转,如果为0,则表示不翻转,翻转完第一行,仅接着翻第2行,每行的状态由其上一行的状态所确定。这样只要判断最后一行是否可以全部为0即可,如果为0,判断是否比答案更小,如果更小,则对答案进行更新。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<string>
#include<set>
#include<cmath>
#include<list>
#include<deque>
#include<cstdlib>
#include<bitset>
#include<stack>
#include<map>
#include<queue>
using namespace std;
typedef long long ll;
const int INF=0x3f3f3f3f;
const double PI=acos(-1.0);
const double eps=1e-;
const ll mod=1e9+;
ll gcd(ll a,ll b){return b?gcd(b,a%b):a;}
int n,m,mp[][],tmp[][],cnt[][],ans[][],res;
void flip(int x,int y) //翻转,用异或符操作
{
tmp[x][y]^=;
tmp[x+][y]^=;
tmp[x-][y]^=;
tmp[x][y+]^=;
tmp[x][y-]^=;
}
int main()
{
ios_base::sync_with_stdio(false); cin.tie();
cin>>m>>n;
for(int i=;i<=m;i++)
{
for(int j=;j<=n;j++)
{
cin>>mp[i][j];
}
}
res=INF; //初始答案为无穷大
for(int s=;s<(<<n);s++) //枚举第一行的所有状态
{
int tot=;
memset(cnt,,sizeof(cnt));
for(int i=;i<=m;i++)
{
for(int j=;j<=n;j++)
{
tmp[i][j]=mp[i][j];
}
}
for(int i=;i<n;i++)
{
if(((s>>i)&)) //如果第i+1位为1,则对其进行翻转
{
tot++;
cnt[][i+]=;
flip(,i+);
}
}
for(int i=;i<=m;i++) //翻转第2至m行
{
for(int j=;j<=n;j++)
{
if(tmp[i-][j])
{
tot++;
cnt[i][j]=;
flip(i,j);
}
}
}
int flag=;
for(int i=;i<=n;i++) //判断是否合法
{
if(tmp[m][i]!=)
{
flag=;
break;
}
}
if(flag&&tot<res) //合法且比当前答案小对答案进行更新
{
res=tot;
for(int i=;i<=m;i++)
{
for(int j=;j<=n;j++)
{
ans[i][j]=cnt[i][j];
}
}
}
}
if(res==INF) //不能实现
{
cout<<"IMPOSSIBLE"<<endl;
return ;
}
for(int i=;i<=m;i++)
{
for(int j=;j<=n;j++)
{
if(j==) cout<<ans[i][j];
else cout<<" "<<ans[i][j];
}
cout<<endl;
}
return ;
}

题目链接:http://poj.org/problem?id=1753

题目大意:

  给你一个4*4的棋盘,上面摆了16个棋子,每个棋子有两面,一面是黑色一面是白色,给出棋盘的初始状态,每次可以翻一个棋子,而且每次翻的时候连着四周的棋子一起翻,问最少几次操作可以使棋盘全变为白棋或者黑棋。

思路:

  首先可以用DFS做,数据范围较小,最多翻16个棋子(每个棋子要么翻要么不翻,再翻就和原来一样了),枚举翻的棋子数,然后用DFS搜索翻该棋子数的所有情况,然后进行判断是否全为黑或全为白,就可以找出答案。

  当然也可以用二进制枚举的方法,总共16个棋子,对应2^16种状态,直接暴力枚举全部枚举全部情况,保留答案的最小值,与上面那题类似。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<string>
#include<set>
#include<cmath>
#include<list>
#include<deque>
#include<cstdlib>
#include<bitset>
#include<stack>
#include<map>
#include<queue>
using namespace std;
typedef long long ll;
const int INF=0x3f3f3f3f;
const double PI=acos(-1.0);
const double eps=1e-;
const ll mod=1e9+;
ll gcd(ll a,ll b){return b?gcd(b,a%b):a;}
int mp[][],tmp[][],ans;
void flip(int x,int y) //翻转
{
tmp[x][y]^=;
tmp[x+][y]^=;
tmp[x-][y]^=;
tmp[x][y+]^=;
tmp[x][y-]^=;
}
int main()
{
ios_base::sync_with_stdio(false); cin.tie();
for(int i=;i<=;i++)
{
for(int j=;j<=;j++)
{
char c;
cin>>c;
if(c=='w') mp[i][j]=;
else mp[i][j]=;
}
}
ans=;
for(int s=;s<(<<);s++) //枚举所有情况
{
int tot=;
for(int i=;i<=;i++)
{
for(int j=;j<=;j++)
tmp[i][j]=mp[i][j];
}
for(int i=;i<;i++)
{
if((s>>i)&)
{
int x=(i+)/+; //判断第i+1棋子的坐标,然后对其翻转
int y=(i+)%;
if(y==){x--;y=;}
tot++;
flip(x,y);
}
}
int flag=;
for(int i=;i<=;i++) //判断是否合法
{
for(int j=;j<=;j++)
{
if(tmp[i][j]!=tmp[][])
{
flag=;
break;
}
}
if(flag==) break;
}
if(flag&&tot<ans) //合法更新答案
ans=tot;
}
if(ans==)
cout<<"Impossible"<<endl;
else
cout<<ans<<endl;
return ;
}