POJ - 3279 Fliptile (状压dp)

题意:题意给你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("\n");
        }
    else printf("IMPOSSIBLE\n");
    return 0;
} 

 

转载于:https://www.cnblogs.com/Tianwell/p/11254100.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值