题意:题意给你mxn的01矩阵可以进行翻动某个坐标,但同时会翻动其周围(上下左右的位置),问如何最少次翻动将所有翻转到0 ,并打印出翻转的图像
思路:我们可以用dfs来暴力搜索,当然也可以使用状压dp。刚好我也在做状压dp的专题所以参考了别人怎么用状压去写,然而总感觉他们写的是模拟...并且有点注释也没有写清楚,看了很久(可能是我太蠢)
当然后面看懂了,并且用两种方法去把它写了(但是总感觉就是个遍历模拟翻转..)
//(1)题解一:(通过操作图来获得某点的颜色,这样就不需要再用备份图记录状态)
#include <stdio.h> #include <iostream> #include<cstring> const int maxn = 20; using namespace std; const int inf = 0x3f3f3f3f; int g[maxn][maxn];//存储的图 int opt[maxn][maxn];//所有坐标是否操作(由于要操作次数最少那么一定可以某个位置做多操作一次) int ans[maxn][maxn];//存储最后操作的答案 int pos[5][2]={0,0,1,0,0,1,-1,0,0,-1};//该点以及周围的点 int n,m;//边界长度 bool getcolor(int x,int y){ int cnt = g[x][y];//获得原图的棋子颜色 for(int i=0;i<5;i++){ int nx = x+ pos[i][0]; int ny = y+ pos[i][1]; if(nx<0||nx>=n||ny<0||ny>=m) continue;//边界判断 cnt+=opt[nx][ny]; //获取操作数对该区间的操作数为奇数时则符合为黑色 } if(cnt%2) return true; else return false; } int solve(int n,int m){ for(int i=1;i<n;i++){ for(int j=0;j<m;j++){ if(getcolor(i-1,j))//当i-1,j位置为黑色(1)时就反转其下一行的点 opt[i][j] =1; } } for(int j=0;j<m;j++){ // 因为优先将前n-1行反转为白色,所以最后一行不一定可以正好反转为白色 if(getcolor(n-1,j)) return inf; // 无解 } int cnt=0; for(int i=0;i<n;i++){ for(int j=0;j<m;j++){ cnt+=opt[i][j]; // 获取操作次数 } } return cnt; } int main(){ cin>>n>>m; for(int i=0;i<n;i++) { for(int j=0;j<m;j++){ scanf("%d",&g[i][j]); } } int flag=0; int answ=inf; for(int i=(1<<m)-1;i>=0;i--){//反向(即有1<<m种反转可能) memset(opt,0,sizeof(opt)); for(int j=m-1,k=0;j>=0;j--){ opt[0][k++]= (i>>j)&1 ;// 第一行处理的方案 按照字典序减少得到 } //枚举出第一行的所有操作状态 int res=solve(n,m); if(res!=inf){ // 首先有解 flag=1; if(res<=answ) { // 次数相同时候,越靠近最后 字典序越小 answ=res; memcpy(ans,opt,sizeof(opt));//将操作图解得到 } } } if(!flag) cout<<"IMPOSSIBLE"<<endl; else { for(int i=0;i<n;i++){ for(int j=0;j<m;j++){ if(j != 0) putchar(' '); printf("%d",ans[i][j]); } cout<<endl; } } return 0; }
//(2)题解二:模拟翻转操作
#include <stdio.h> #include <iostream> #include<cstring> const int maxn = 20; using namespace std; const int inf = 0x3f3f3f3f; int g[maxn][maxn];//存储的图 int ans[maxn][maxn];//存储最后操作的答案 int n,m;//边界长度 int cur[maxn][maxn];//存储备份图 int oper[maxn][maxn],steps = 0, minSteps = 1<<30; //当前操作, 最小操作, 当前解和最小解//所有坐标是否操作(由于要操作次数最少那么一定可以某个位置做多操作一次) void press(int x, int y) { //按下x, y处的按钮 cur[x][y]^=1, cur[x+1][y]^=1, cur[x-1][y]^=1, cur[x][y+1]^=1, cur[x][y-1]^=1; } bool solve() { //判断是否已经解决问题 memcpy(cur, g, sizeof(g)); //根据枚举结果改变第一二行 for(int i = 1; i <= n; i++) if(oper[1][i]) press(1, i), steps++; //根据第i-1行决定第i行的操作 for(int i = 2; i <= m; i++){ for(int j = 1; j <= n; j++) if(cur[i-1][j]) oper[i][j]=1, press(i, j), steps++; } //判断最后一行是否满足条件 for(int i = 1; i <= n; i++) if(cur[m][i]) return 0; return 1; } int main() { scanf("%d%d",&m,&n); for(int i = 1; i <= m; i++) for(int j = 1; j <= n; j++) scanf("%d",&g[i][j]); //仅仅枚举第一行的状态即可 for(int i = 0; i < (1<<n); i++){ //状态压缩 memset(oper, 0, sizeof(oper)), steps = 0; //初始化不要忘 for(int j = 0; j < n; j++){ oper[1][n-j] = (i>>j&1); } if(solve() && steps>0 && steps<minSteps) minSteps = steps, memcpy(ans, oper, sizeof(oper));; } if(minSteps < (1<<30)) for(int i = 1; i <= m; i++){ for(int j = 1; j <= n; j++) printf("%d ",ans[i][j]); printf(" "); } else printf("IMPOSSIBLE "); return 0; }