费解的开关 --- 蓝桥杯

acwing 95

费解的开关

在这里插入图片描述
详解查看代码
==方法一:==二进制枚举 + 位运算 + 递推

熄灯问题同方法解决,参考于郭炜老师;

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
using namespace std;

//一共五行五列,每一行可以用一个字符来表示,每一行的列数对应该字符的二进制位
//本题中需要找出最少的步数,并且判断是否能在6步之类完成,所以需要判断按0步数少还是按1步数少
char orilight1[5];//存放正常数组
char orilight2[5];//存放相反数组
char lights[5];//过程中改变数组
char result[5];//结果数组(如果题意需要打印结果数组)

int Getbit(char x,int i){//得到第x行的第i位
    return (x >> i) & 1;
}

void Setbit(char &x,int i,int v){//将x行的第i位设置为v(题目中的0,1变换)
    if(v == 1){
        x |= (1 << i);
    }else{
        x &= ~(1 << i);
    }
}

void Filp(char &x,int i){//翻转x行的第i位
    x ^= (1 << i);
}

void outputresult(char result[]){
    for (int i = 0; i < 5;++i){
        for (int j = 0; j < 5;++i){
            cout << Getbit(result[i], j);
        }
        if(i < 4) cout << endl;
    }
}

int ans = 1 << 30;//存贮结果
void solve(char orilights[]){
    ans = 1 << 30;
    int cnt = 0;//统计按下开关的次数
    /*思路 : 想要将所有行全部熄灭,我们可以从第一行开始,枚举第一行的所有状态,
             (因为是第一行我们可以任意改变其状态然后向下推广查看是否能够全部熄灭)
             得到第一行的一个结果状态后,根据第一行的结果翻转周围,和第二行;
             然后最后lights数组里的第一行结果就是第二行的switchs;
             (因为我们要用第二行来熄灭第一行的所有灯(也就是1)),依次类推逐渐得到
             所有答案;
    */
    for (int i = 0; i <= (1 << 5) - 1;i ++){
        cnt = 0;
        memcpy(lights, orilights, sizeof(orilights));
        char switchs = i;//swithchs代表的是第一行的一个答案结果,它的二进制位如果是1则翻转,反之不翻转
        for (int k = 0; k < 5;k ++){
            result[k] = switchs;
            for (int j = 0; j < 5;j ++){
                if(Getbit(switchs, j) == 1){
                    cnt++;
                    Filp(lights[k], j);
                    if(j > 0) Filp(lights[k],j - 1);
                    if(j < 4) Filp(lights[k],j + 1);
                }
            }
            if(k < 4) lights[k + 1] ^= switchs;
            switchs = lights[k];
        }
        if (lights[4] == 0){
        	ans = min(ans ,cnt);	
//        	outputresult(result);
//          break;
        } 
    }
}

int main(){
    int t;cin >> t;
    while(t --){
        for (int i = 0; i < 5;i ++){
            for (int j = 0; j < 5;j ++){
                char c;cin >> c;
                if(c == '0'){
                    Setbit(orilight1[i], j, 0);
                    Setbit(orilight2[i], j, 1);
                }
                else{
	                Setbit(orilight1[i],j,1);
	                Setbit(orilight2[i],j,0);
	            } 
            }
        }

        solve(orilight1);
        solve(orilight2);
//      if(ans==1<<30)   printf("Impossible\n");
//    	else    printf("%d\n",ans);
        if(ans <= 6)   printf("%d\n",ans);
	    else    printf("-1\n");
    }
    return 0;
}

熄灯问题

#include <iostream>
#include <string>
#include <cstring>
#include <iostream>
using namespace std;
char orLights[5];
char lights[5];
char result[5];

int GetBit(char c,int i)
{
	return(c >> i) & 1;
}
void SetBit(char & c,int i,int v)
{
	if(v)
	{
		c |= (1 << i);
	}
	else
		c &= ~(1 << i);
}
void FlipBit(char & c,int i)
{
	c ^= (1 << i);
}
void OutputResult(int t,char result[])
{
	cout << "PUZZLE #" << t << endl;
	for(int i = 0;i < 5;++i)
		for(int j = 0;j < 6;++j)
		{
			cout << GetBit(result[i],j);
			if(j < 5)
				cout << " ";
		} 
		cout << endl;
}
int main()
{
	int T;
	cin >> T;
	for(int t = 1;t <= T;++t)
	{
		for(int i = 0;i < 5;++i)
			for(int j = 0;j < 6;++j)
			{
				int s;
				cin >> s;
				SetBit(orLights[i],j,s);
			}
		for(int n = 0;n < 64;++n)
		{
			int switchs = n;
			memcpy(lights,orLights,sizeof(orLights));
			for(int i = 0;i < 5;++i)
			{
				result[i] = switchs;
				for(int j = 0;j < 6;j++)
				{
					if(GetBit(switchs,j))
					{
						if(j > 0)
							FlipBit(lights[i],j - 1);
						FlipBit(lights[i],j);
						if(j < 5)
							FlipBit(lights[i],j + 1);
					}	
				}	
				if(i < 4)
					lights[i + 1] ^= switchs;
				switchs = lights[i];
			}
			if(lights[4] == 0)
				OutputResult(t,result);
			break;
		}
	}
	return 0;
}

方法二:

// 思路:我们枚举第一行的点击方法,共32种,完成第一行的点击后,固定第一行,
// 从第一行开始递推,若达到第n行不全为0,说明这种点击方式不合法。
// 在所有合法的点击方式中取点击次数最少的就是答案。
// 对第一行的32次枚举涵盖了该问题的整个状态空间,因此该做法是正确的
// 
// 时间复杂度:32*20*5*500 = 一百六十万
// 对第一行操作有32种可能 * 对前四行有20种操作可能 * 每一次操作都要改变5个灯的状态 * 最多读入的时候可能有500次light矩阵
//
// 最关键的两个性质
// 每一个位置最多只会被点击一次
// 如果固定了第一行,那么满足题意的点击方案最多只有一种
#include <bits/stdc++.h>
using namespace std;

const int N = 10;

char g[N][N];

void turn(int x,int y)
{
    int dx[5]={0,1,0,-1,0},dy[5]={0,0,1,0,-1};
    for(int i=0;i<5;i++)
    {
        int xx=x+dx[i],yy=y+dy[i];
        if(xx>=0 && xx<5 && yy>=0 && yy<5)
        {
            g[xx][yy]^=1;
        }
    }
}

int work()
{
    int ans = 0x3f3f3f3f;
    for(int k=0;k< 1<<5;k++)
    {
        int res=0;
        char temp[N][N];
        memcpy(temp,g,sizeof g);
        //第一行按灯一共有32种可能,对于每一种可能,我们操作选择后,开始固定,
        //此时第一行不一定是全亮的状态,第一行只是32种操作可能的一种
        //然后遍历前四行,如果g[i][j] == '0', 那么turn(i+1, j)


        //这一步是枚举第一行的点击方法,只要第一行固定了,那么满足题意的点击方法就只有一种了。
        //假如第一行是00111, k从0到31进行枚举,如果k = 00001,
        //那么代表g矩阵中第一行的第一个灯要点击一下,
        //第一行变为11111。
        //k不断变大(0变到31)
        //假如第一行是00111, k从0到31进行枚举,如果k = 10001,
        //那么代表g矩阵中第一行的第一个灯和最后一个灯要点击一下,
        //第一行变为11100。

        //之后固定这一行,改变下面的灯看是否能全变亮。
        //这也就是为什么我们copy light, 每一次对k的枚举都会改变light。

        for(int j=0;j<5;j++)
        {
            if(k>>j & 1)
            {
                res++;
                turn(0,j);
            }
        }
        for(int i=0;i<4;i++)
        {
            for(int j=0;j<5;j++)
            {
                if(g[i][j]=='0')
                {
                    res++;
                    turn(i+1,j);
                }
            }
        }

        bool flag=true;
        for(int i=0;i<5;i++)
        {
            if(g[4][i]!='1')
            {
                flag=false;
                break;
            }
        }
        if(flag) ans = min(ans,res);
        memcpy(g,temp,sizeof g);
    }
    if(ans>6) return -1;
    else return ans;
}

int main()
{
    int n; cin>>n;
    while(n--)
    {
        for(int i=0;i<5;i++) cin>>g[i];
        cout<< work() <<endl;   
    }   
    return 0;
}

作者:GRID
链接:https://www.acwing.com/solution/content/17052/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值