ACWing-116(二进制的应用)
思考
1.题目大意
有一个 4 * 4 的矩阵,其中第 i 行第 j 列有一个门把手,门把手有两个状态,用 ‘+’ 表示门把手处于闭合状态 ,‘-’ 表示门把手处于打开状态
有一个操作:可以更改一个门把手的状态,同时该行和该列的所有门把手的状态都会被改变。
问最少的操作次数使得所有门把手都处于打开状态 以及 该操作下修改的门把手的 i 和 j 编号,按照从左到右,从上到下的顺序输出
数据范围:1 < i < 4,1 < j < 4
2.时间复杂度
因为接下来每一个门把手都只有两种操作:打开或者闭合,最多 4 * 4 个门把手,所有所有门把手相当于组成了一个 4 * 4 矩阵,遍历每一种矩阵的状态,时间复杂度为O( 2^(4*4) ),大约 O(10e6) ,时间复杂度不大
对于每一种状态,还需要遍历 16 个门把手的具体状态,即是否进行操作,所以时间复杂度总共是 O(16e6),
3.数据的录入
因为我们要把这个矩阵(即所有把手的状态)用一个二进制数表示,所以这个二进制是 16 位的,分别对应了每一个门把手的初始状态,其中矩阵的第 i 行,第 j 列对应二进制的 (4 * i + y ) 位
a0 a1 a2 a3
a4 a5 a6 a7
a8 a 9 a10 a11
a12 a13 a14 a15
(如表,16个门把手构成一个矩阵,分别对应二进制的第0~15位)
我们用 1 表示门把手处于闭合状态,当录入的数据等于 ‘+’ 时,表示这个门把手闭合,我们就要将二进制的对应位置初始化为 1,将一个二进制的第 i 为初始化为1,只需要将这个数加上 2^i
0 1 0 0
0 0 0 0
0 0 0 0
0 1 0 0 (门把手矩阵的初始状态,其中二进制值为2^1 + 2^13)
4.现在我们已经知道了初始矩阵(门把手的初始状态),也对矩阵的所有可能操作进行了遍历,那么,怎么表示同时将我们操作的矩阵第 i 行,第 j 列的门把手的对应行和对应列都进行操作呢
这里就需要用到我们前面提到过的,将一个二进制的第 i 为初始化为1,只需要将这个数加上 2^i,同理,我们要对一行或者一列的门把手状态改变,只需要加上改行或者该列的所有 2^i 即可,在代码中,我们创建了一个 change 数组,其中change[i][j] 就表示我们对第 i,j个门把手修改,它的对应行和列的所有修改
5.现在我们已经知道了如何进行状态修改操作,那么我们怎么获得二进制中第 i 位的值,即 矩阵中
位置为 [i / 4, i % 4] 的门把手呢,这里有一个二进制公式 k >> i & 1,表示二进制数 k 的第 i 位 与 1 进行与运算,结果为 1 表示该位为 1,否则为 0,(与运算还常用于判断奇数,一个数 & 1,由此可知我们是从最低位开始进行与运算的)这样我们就能知道具体的门把手的状态了
那么怎么表示这个灯泡的状态改变了呢?这里我们就需要用到二进制的异或运算符了,它的概念是:异或的两端有且仅有一个 1 ,值为 1,否则为0,也可以理解为异或的两端值相等时,值为 1,否则为0.
异或运算参考博客:什么是异或_异或运算及异或运算的作用 - TMD睡觉 - 博客园 (cnblogs.com)
我们只需要将表示矩阵状态的二进制数异或上 change[i][j] 的值即可,这样原矩阵的状态就改变了
6.最后,创建一个动态数组来存放对具体对哪一个门把手操作
auto输出时不要忘了加 1,因为我们的下标都是从 0 开始的
AC代码
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
int change[4][4]; //改变一个位置需要state需要异或的值
typedef pair<int,int> PII;
int get(int x, int y)
{
return x * 4 + y;
}
int main()
{
int state = 0; //表示矩阵的状态
for(int i = 0; i < 4; i ++ )
{
string s; cin >> s;
for(int j = 0; j < 4; j ++ )
{
if(s[j] == '+')
state += 1 << get(i,j); //更新矩阵的状态
}
}
//初始化更新矩阵
for(int i = 0; i < 4; i ++ )
{
for(int j = 0; j < 4 ; j ++ )
{
for(int k = 0; k < 4; k ++ )
{
change[i][j] += 1 << get(i,k);
change[i][j] += 1 << get(k,j);
}
change[i][j] -= 1 << get(i,j); //因为(i,j)这个点添加了两次,所以需要减去一次
}
}
//枚举所有状态
vector<PII> res;
for(int k = 0; k < 1 << 16; k ++) //16个灯泡,2^16次方
{
int now = state;
vector<PII> path; //更改的门把手
for(int i = 0; i < 16; i ++)
{
if(k >> i & 1) //k的第i位为1
{
int x = i / 4,y = i % 4;
now ^= change[x][y];
path.push_back({x,y});
}
}
if(!now && (res.empty() || path.size() < res.size()))
res = path;
}
cout << res.size() << endl;
for(auto &p : res)
cout << p.first + 1 << " " << p.second + 1 << endl; //因为下标从0开始,所以需要加以
return 0;
}