• POJ


    题意:题意给你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;
    } 
  • 相关阅读:
    题解 P1030 【求先序排列】
    行列式及其打开方式
    题解 P2580 【于是他错误的点名开始了】
    题解 P1130 【红牌】
    题解 P5239 【回忆京都】
    题解 P1184 【高手之在一起】
    【笔记】自学ST表笔记
    题解 P1208 【[USACO1.3]混合牛奶 Mixing Milk】
    树状数组自学笔记
    EBS R12.2系统logo的修改
  • 原文地址:https://www.cnblogs.com/Tianwell/p/11254100.html
Copyright © 2020-2023  润新知