NC235250 牛可乐的翻转游戏

题目

题目描述

牛可乐发明了一种新型的翻转游戏!

在一个有 \(n\)\(m\) 列的棋盘上,每个格子摆放有一枚棋子,每一枚棋子的颜色要么是黑色,要么是白色。每次操作牛可乐可以选择一枚棋子,将它的颜色翻转(黑变白,白变黑),同时将这枚棋子上下左右相邻的四枚棋子的颜色翻转(如果对应位置有棋子的话)。

牛可乐想请你帮他判断一下,能否通过多次操作将所有棋子都变成黑色或者白色?如果可以,最小操作次数又是多少呢?

输入描述

第一行两个整数 \(n,m(1\leq n\leq 100,1\leq m\leq 10)\),代表棋盘的行数和列数。

之后 \(n\) 行,每行 \(m\) 个数字,第 \(i\) 个数字如果为 \(0\) ,代表对应位置的棋子为白色,如果为 \(1\) 则为黑色。

输出描述

如果无法将所有棋子变成一个颜色,输出 "Impossible"(不含引号),否则输出变成一个颜色的最小操作次数。

示例1

输入

4 4
1001
1101
1001
1000

输出

4

题解

知识点:枚举,状压,递推。

首先注意到可以通过对第 \(i\) 行操作第 \(i-1\) 行全部按灭或按亮,于是可以通过这个操作将状态变换成最后一行的亮灭,而前三行统一亮灭。而基于这些操作的最后一行如果不是全灭或全亮就说明初状态不可行,否则可行。

然后,对于第一行会有 \(2^m\) 种初状态,只要遍历这些状态,分别执行上述操作即可。

具体操作上,可以用一个 \(m\) 位二进制 \(change[i]\) 表示对 \(i\) 行灯的开关操作,\(1\) 表示按,\(0\) 表示不按。对于 \(i\) 行的状态 \(a[i]\) 而言,需要将 \(1\) 的位置取反且周围 \(4\) 个也取反,\(0\) 的位置不变,即\(a[i] \oplus change[i] \oplus (change[i]>>1) \oplus (change[i]<<1)\) ;对于 \(i-1\) 行的状态 \(a[i-1]\) 而言,即 \(a[i-1] \oplus change[i]\) ,观察这个性质,我们可以将下一次按钮方式设置为本行的灯状态,做到用下一行按本行的灯;对于 \(i+1\) 行的状态 \(a[i+1]\) 而言,即 \(a[i+1] \oplus change[i]\)。需要注意 \(change[i] << 1\) 可能在int里面使范围外的二进制码改变需要加上限制 \((change[i] << 1) \& ((1<<m)-1))\)

最后计算 \(change[0\cdots m-1]\) 可以消去二进制最后一位 \(1\) 来计数。

时间复杂度 \(O(nm)\)

空间复杂度 \(O(nm)\)

本题为POJ1753的变种。

代码

#include <bits/stdc++.h>

using namespace std;

int n,m;
int a[107],b[107],cur[107],change[107];

int cal(int num){
    int ans = 0;
    while(num){
        ans++;
        num &= num-1;
    }
    return ans;
}

int sol(int a[]){
    int ans = ~(1<<31);
    for(change[0] = 0;change[0]<(1<<m);change[0]++){
        int sum = 0;
        cur[0] = a[0];
        for(int i = 0;i<n;i++){
            sum += cal(change[i]);
            cur[i] = cur[i] ^ change[i] ^ (change[i]>>1) ^ ((change[i]<<1)&((1<<m)-1));
            cur[i+1] = a[i+1] ^ change[i];
            change[i+1] = cur[i];
        }
        if(!cur[n-1]) ans = min(ans,sum);
    }
    return ans;
}


int main(){
    std::ios::sync_with_stdio(0);
    cin>>n>>m;
    for(int i = 0;i<n;i++){
        for(int j = 0;j<m;j++){
            char tmp;
            cin>>tmp;
            if(tmp == '1') a[i] |= (1<<j);
            else if(tmp == '0') b[i] |= (1<<j);
        }
    }
    int ans = min(sol(a),sol(b));
    if(ans>n*m) cout<<"Impossible"<<'\n';
    else cout<<ans<<'\n';
    return 0;
}
posted @ 2022-05-17 11:23  空白菌  阅读(548)  评论(0编辑  收藏  举报