【状态压缩 开关问题】 飞行员兄弟
传送门
题意
一个\(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;
}

浙公网安备 33010602011771号