传送门:http://poj.org/problem?id=3279
题面描述:
给你一个方格,这个方格中有M*N个小方块,每个小方块都是0或1,定义一种反转操作:,每次反转范围是中心方块以及其上下左右的方块,反转后状态变化为0->1,1->0。问能不能通过有限次反转操作,将方格的最终状态变为全是0,如果可以,输出一个反转次数最少而且字典序最小的矩阵,矩阵对应的数值为该位置的反转次数,否则,输出IMPOSSIBLE。
思路:
1.对于同一个位置来说,最多反转1次,因为只有两种状态,反转两次事实上跟原先状态是一样的。
2.对于同一个位置,它的最终状态会受其上下左右各个方块反转的影响,这会使问题的分析变得非常复杂,但是我们可以先确定第一行的反转次序,此时对于第一行来说,能够影响它的左中右的反转都已经确定了,那么决定他最终状态的就只有它的下的反转,调节他下面方块的反转次序,使第一行的状态全变成0,然后对于第二行一样,以此类推,到了倒数第一行,此时不仅确定了倒数第二行的状态,也把最后一行的反转次数确定了,同时,最后一行的状态也被确定了(可能有点绕),此时我们只要判断最后一行是不是全是0就可判断这种第一行的反转次序是不是可以满足条件。
3.接下来,我们来思考如何来枚举第一行的各种反转次数,由1可以知道,每个位置事实上只有0和1两种状态,所以我们可以考虑二进制枚举来解决问题,具体实现在AC代码里面。因为我们是从小到大枚举的,所以最终得到的结果一定是最小字典序的,所以不用再考虑字典序的问题了。
最终代码:
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int maxn = 20; int map[maxn][maxn],flip[maxn][maxn],ans[maxn][maxn],m,n; int dx[] = {1,0,0,0,-1}; int dy[] = {0,1,0,-1,0}; int get(int x,int y){ int c =map[x][y]; for(int d = 0;d<5;d++){ int x1 = x+dx[d]; int y1 = y+dy[d]; if(x1>=0&&x<m&&y>=0&&y<n){ c+=flip[x1][y1]; } } return c%2; } int calc(){ int res = 0; for(int i = 1;i<m;i++) for(int j = 0;j<n;j++) { if(get(i-1,j)) { flip[i][j] = 1; } } for(int i = 0;i<n;i++) if(get(m-1,i)!=0) return -1; for(int i = 0;i<m;i++) for(int j = 0;j<n;j++) res+=flip[i][j]; return res; } int main() { while(~scanf("%d %d",&m,&n)){ for(int i = 0;i<m;i++) for(int j = 0;j<n;j++) scanf("%d",&map[i][j]);int res = -1; for(int i = 0;i< 1<< n;i++){ memset(flip,0,sizeof(flip)); for(int j = 0;j<n;j++) flip[0][n-j-1] = i>>j&1; int num = calc(); if(num>=0&&(res<0||res>num)){ res = num; memcpy(ans,flip,sizeof(flip)); } } if(res==-1) printf("IMPOSSIBLE "); else { for(int i = 0;i<m;i++) for(int j = 0;j<n;j++) printf("%d%c",ans[i][j],j+1==n?' ':' '); } } return 0; }
如还有困惑的地方,请在评论区留言交流!