poj-3279 poj-1753(二进制枚举)
Posted zjl192628928
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了poj-3279 poj-1753(二进制枚举)相关的知识,希望对你有一定的参考价值。
题目链接: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-6; const ll mod=1e9+7; ll gcd(ll a,ll b){return b?gcd(b,a%b):a;} int n,m,mp[20][20],tmp[20][20],cnt[20][20],ans[20][20],res; void flip(int x,int y) //翻转,用异或符操作 { tmp[x][y]^=1; tmp[x+1][y]^=1; tmp[x-1][y]^=1; tmp[x][y+1]^=1; tmp[x][y-1]^=1; } int main() { ios_base::sync_with_stdio(false); cin.tie(0); cin>>m>>n; for(int i=1;i<=m;i++) { for(int j=1;j<=n;j++) { cin>>mp[i][j]; } } res=INF; //初始答案为无穷大 for(int s=0;s<(1<<n);s++) //枚举第一行的所有状态 { int tot=0; memset(cnt,0,sizeof(cnt)); for(int i=1;i<=m;i++) { for(int j=1;j<=n;j++) { tmp[i][j]=mp[i][j]; } } for(int i=0;i<n;i++) { if(((s>>i)&1)) //如果第i+1位为1,则对其进行翻转 { tot++; cnt[1][i+1]=1; flip(1,i+1); } } for(int i=2;i<=m;i++) //翻转第2至m行 { for(int j=1;j<=n;j++) { if(tmp[i-1][j]) { tot++; cnt[i][j]=1; flip(i,j); } } } int flag=1; for(int i=1;i<=n;i++) //判断是否合法 { if(tmp[m][i]!=0) { flag=0; break; } } if(flag&&tot<res) //合法且比当前答案小对答案进行更新 { res=tot; for(int i=1;i<=m;i++) { for(int j=1;j<=n;j++) { ans[i][j]=cnt[i][j]; } } } } if(res==INF) //不能实现 { cout<<"IMPOSSIBLE"<<endl; return 0; } for(int i=1;i<=m;i++) { for(int j=1;j<=n;j++) { if(j==1) cout<<ans[i][j]; else cout<<" "<<ans[i][j]; } cout<<endl; } return 0; }
题目链接: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-6; const ll mod=1e9+7; ll gcd(ll a,ll b){return b?gcd(b,a%b):a;} int mp[5][5],tmp[5][5],ans; void flip(int x,int y) //翻转 { tmp[x][y]^=1; tmp[x+1][y]^=1; tmp[x-1][y]^=1; tmp[x][y+1]^=1; tmp[x][y-1]^=1; } int main() { ios_base::sync_with_stdio(false); cin.tie(0); for(int i=1;i<=4;i++) { for(int j=1;j<=4;j++) { char c; cin>>c; if(c==‘w‘) mp[i][j]=0; else mp[i][j]=1; } } ans=17; for(int s=0;s<(1<<16);s++) //枚举所有情况 { int tot=0; for(int i=1;i<=4;i++) { for(int j=1;j<=4;j++) tmp[i][j]=mp[i][j]; } for(int i=0;i<16;i++) { if((s>>i)&1) { int x=(i+1)/4+1; //判断第i+1棋子的坐标,然后对其翻转 int y=(i+1)%4; if(y==0){x--;y=4;} tot++; flip(x,y); } } int flag=1; for(int i=1;i<=4;i++) //判断是否合法 { for(int j=1;j<=4;j++) { if(tmp[i][j]!=tmp[1][1]) { flag=0; break; } } if(flag==0) break; } if(flag&&tot<ans) //合法更新答案 ans=tot; } if(ans==17) cout<<"Impossible"<<endl; else cout<<ans<<endl; return 0; }
以上是关于poj-3279 poj-1753(二进制枚举)的主要内容,如果未能解决你的问题,请参考以下文章
POJ-3279.Fliptile(二进制状态压缩 + dfs) 子集生成