【状态压缩 开关问题】 飞行员兄弟

传送门

题意

一个\(4\times 4\)的字符矩阵,每一个位置上包含一个字符,如果字符是\(+\)代表闭合,\(-\)代表打开,初始状态至少有一个是关闭的,
当所有都处于打开状态时候,冰箱才是打开的,每改变一个的状态同一行和同一列所有的状态都会随之改变,求将冰箱打开的最小操作以及每个操作的行号、列号
如果存在多种方案,按照优先级输出从上到下,从左到右的方案,即字典序最小的方案

数据范围

\(1 \leq i,j\leq 4\)

题解

  • 枚举所有的点击方式,将整个矩阵的操作压缩成一个二进制数,下标从\(0\)开始, 其中\(1\)代表按这个开关,\(0\)代表不按这个开关

  • 预处理所有位置上操作所带来的改变,每个数的操作相当于异或\(1\),用异或一个数来表示,即异或所有当前行当前列上的数的二进制和,

    • 因为当前行当前列交叉在当前的位置上,所以需要再减去一个当前位置
  • 初始状态也用二进制压缩,其中\(0\) 表示开,\(1\)表示关,当所有操作进行完后整个矩阵都是\(0\)才是合法的一个解

  • 矩阵压缩方式

    • 下标从\(0\)开始,当前行号\(\times 4+\)列号即在二进制中的位
  • 矩阵还原

    • 下标从\(0\)开始,二进制数代表的\(10\)进制数除总行数即所在行数\(y\)模总列数,即当前所在列数。

时间复杂度:\(O(2^{16} \times 16)\sim O(2^{20})=1048576\)

Code

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<b;i++)
#define pii pair<int,int>
#define pb push_back
#define fi first
#define se second 
using namespace std;
const int N=10;
int change[N][N];


int get(int x,int y)
{
    return x*4+y;
}

int main()
{
    int st=0;

    rep(i,0,4)
    {
        string line;
        cin>>line;
        rep(j,0,4)
            if(line[j] == '+')
                st += 1<<get(i,j);
    }    

    rep(i,0,4) rep(j,0,4) 
    {
        rep(k,0,4)
        {
            change[i][j]+= 1<<get(i,k);
            change[i][j]+= 1<<get(k,j);
        }
        change[i][j]-= 1<<get(i,j);
    }

    vector<pii> ans;

    rep(i,0,1<<16)
    {
        vector<pii> path;
        int now=st;
        rep(j,0,16)
        {
            if(i >> j &1)
            {
                int x=j/4, y=j%4;
                now ^=change[x][y];
                path.pb(make_pair(x+1,y+1));
            }
        }

        if(!now && (path.size()<ans.size()||ans.empty())) 
            ans=path;
    }   

    cout<<ans.size()<<endl;
    rep(i,0,ans.size()) cout<<ans[i].fi<<" "<<ans[i].se<<endl;
}
posted @ 2020-05-28 00:05  Hyx'  阅读(118)  评论(0)    收藏  举报