ACWing-116(二进制的应用)

题目引入:116. 飞行员兄弟 - AcWing题库

思考

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;
}

posted @ 2022-05-05 08:42  光風霽月  阅读(19)  评论(0编辑  收藏  举报