飞行员兄弟(超详细易懂)

在这里插入图片描述
对于输入样例,是一个4*4的矩阵,在这里我们用"1"表示“+”,“0”表示“-”。
对于终态是一个全为0的矩阵。由于每对一个开关操作,对应的行和列都会发生改变,由题可得,我们做的就是通过若干次开关操作构建一条从初态到终态的路线。

以示例为例,输出了(1,1)->(1,3)->(1,4)->(4,1)->(4,3)->(4,4),依次对上述位置的开关进行了操作。我们用二进制来表示上述方案即为:
0 1 1 1
0 0 0 0
0 0 0 0
0 1 1 1
按题意从上到下,从左到右打开:
1110 0000 0000 1110

这样我们就将一种方案通过二进制的方式表示了,我们将初态以16位二进制表示得 0010 0000 0000 0010(从上到下,从左到右)

对于16个把手,一共有216种排列组合能够使得初态到达不同的终态,我们需要的是其中能够使得初态变为全0的方案且该方案的二进制表示中1的数量尽可能少。由于对同一个位置执行两次操作是没有意义的,所以每一个开关只会执行一次。

我们可以枚举这216种可能,为了符合从上到下,从左到右搜索,对于每一个单独的方案,我们从地位到高位一次遍历其中的“1”,每找到一个就执行开关操作,我们用k表示位置,则每一个开关的位置变化可以表示为

x=i/4,y=i%4;

i从0遍历到16,符合从上到下,从左到右搜索。

同时我们每执行一次开关操作需要更新初态的状态到下一个状态,因为对应开关的行和列的数都会发生改变,即取反。我们知道对一个二进制数的某一位取反,只需要将该位异或一个1即可。这里我们可以计算一个矩阵用于存储对每一个开关操作后的数值,即取反后的数值。
以(1,1)为例,执行后
0 0 0 0 —》 0 1 0 0
0 0 0 0 —》 1 1 1 1
0 0 0 0 —》 0 1 0 0
0 0 0 0 —》 0 1 0 0
二进制表示:
0010 0010 1111 0010
将其与初态进行异或操作:
0010 0010 1111 0010 ^ 0010 0000 0000 0010
可以得到下一个状态。

思路总结:

1.将初态以二进制表示,用一个整数来存储
2.记录对每个位置执行开关操作后行和列取反后得数值,该数用于使上一个状态到下一个状态。
3.枚举216种可能,遍历每种可能中的1的个数,执行开关操作,记录该操作的位置,并进入下一个状态
4.判断终态是否为全0,以及目前的方案是否是最少,若不是则更新,由于我们是从上到下,从左到右搜索,最先记录的方案是符合该顺序的,即时后面有相同长度,其他顺序的方案,我们也不会更新。

代码

#include<iostream>
#include<string>
#include<queue>
//#include<bitset>
using namespace std;
int main()
{
    int state=0;
    int x,y;//保存编号位置
    //把输入的初始状态通过位运算转化为整数
    for(int i=0;i<4;i++)
    {
        string line;
        cin>>line;
        for(int j=0;j<4;j++)
        {
            if(line[j]=='+')
                state+=1<<(4*i+j);
        }
    }
    //创建一个数组存储每一位进行操作后的变化,
    int change[4][4]={0};
    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<<(4*i+k);//每一行的变化
                change[i][j]+=1<<(4*k+j);//每一列的变化
            }
            change[i][j]-=1<<(4*i+j);//减去第i,j位置重复多算一次的值。
        }
        //cout<<bitset<16>(change[1][1])<<endl;
    }
    //枚举2^16种方案,每种方案是一个16位的二进制数,里面的1代表操作的位置
    queue<int> Q;//
    for(int i=0;i<(1<<16);i++)
    {
        int now=state;
        queue<int> q;
        for(int j=0;j<16;j++)
        {
            if(i>>j&1)
            {
                x=j/4,y=j%4;//记录1的编号位置
                now^=change[x][y];//更新点击位置(x,y)后的状态
                q.push(x);
                q.push(y);
            }
        }
        if(!now && (Q.empty() || Q.size()>q.size()))
            Q=q;
    }
    cout<<Q.size()/2<<endl;
    while(!Q.empty())
    {
        x=Q.front();
        Q.pop();
        y=Q.front();
        Q.pop();
        cout<<x+1<<" "<<y+1<<endl;
    }
    return 0;
}
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值