# 蓝桥杯—开关问题

蓝桥杯—开关问题

@


蓝桥杯的开关问题一般都是偏暴力的做法,可能会涉及使用位运算进行状态压缩。

只能同时按两个开关

ACwing 1208 翻硬币

初态和终态,每个状态一行

开关连锁反应:只能同时按下两个开关

很简单,从第二个开关开始枚举,如果前一个开关和终态对应位置开关不一样,那么该开关需要按下以改变前一个开关的状态使得前一个开关状态和终态一致。

#include <iostream>
#include <cstdio>
using namespace std;
int a[105],b[105];
int m,n;
char c;
int main(){

	while((c=getchar())!='\n'){
		if(c=='*')a[m++]=1;
		else a[m++]=0;
	}
	
	while((c=getchar())!='\n'){
		if(c=='*')b[n++]=1;
		else b[n++]=0;
	}
	
	int res=0;
	for(int i=1;i<m;++i){
		if(a[i-1]!=b[i-1]){
			++res;
			a[i-1]^=1;
			a[i]^=1;
		}
	}
	printf("%d\n",res);
	return 0;
}

开关改变上下左右

ACwing 95 费解的开关

给定初态,问最少需要几步使所有灯变亮。

开关的连锁反应为:按下一个开关,其上下左右都会改变状态。

题解:

假定某一行的开关状态确定了,该行某个开关状态只会被下一行该位置的正下方的开关改变。

所以我们可以一行一行考虑,只要固定了该行,那么该行下面的所有行的开关,都是为了弥补上一行没有闭合的开关而服务(如果某个位置正上方的开关为0,那么这个位置一定要按下,为了使得其正上方的开关变成1,)。

因此我们可以枚举第一行每个开关是否按下(使用二进制枚举,1010表示按下第一和第三个开关),只要第一行开关确定了,下面所有开关都确定了,对于第一行的每种按法,最后遍历一下是否所有开关是否都是亮的,如果不是说明该方案不合法。

#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
#define sf(x) scanf("%d",&(x))
int a[10][10],b[10][10];
int n,cnt,ans;

inline void check(){
	bool isok=true;
	for(int i=1;i<5;++i){
		for(int j=0;j<5;++j){
			if(cnt<6&&!b[i-1][j]){
		//		cout<<"ij"<<i<<j<<endl;
				++cnt;

				b[i][j]^=1;

				b[i-1][j]^=1;
				if(j+1<5)b[i][j+1]^=1;
				if(j-1>=0)b[i][j-1]^=1;
				if(i+1<5)b[i+1][j]^=1;
			}
			else if(cnt>=6&&!b[i-1][j]){
				isok=false;
				break;
			}
		}
	}
	for(int i=0;i<5;++i)if(b[4][i]==0)isok=false;
	//cout<<"cnt"<<cnt<<"isok"<<isok<<endl;
	if(isok)ans=min(ans,cnt);
	
}

void pf(){
	puts("\n-------");
	for(int i=0;i<5;++i){
			for(int j=0;j<5;++j)
		//		scanf("%1d",&a[i][j]);
				cout<<b[i][j];
			cout<<endl;
		}
	puts("-------");
}

int main(){
	sf(n);
	while(n--){
		ans=10;
		for(int i=0;i<5;++i)
			for(int j=0;j<5;++j)
				scanf("%1d",&a[i][j]);

		for(int i=0;i<(1<<5)-1;++i){//枚举第一行每种按法
			memcpy(b,a,sizeof a);

		//	pf();
			
			cnt=0;
			for(int j=0;j<5;++j)
				if((i>>j)&1){
					++cnt;
					b[0][j]^=1;
					if(j+1<5)b[0][j+1]^=1;
					if(j-1>=0)b[0][j-1]^=1;
					b[1][j]^=1;
					
				}
			//cout<<"i:"<<i<<" "<<cnt<<endl;
//			pf();
			
			if(cnt<6)check();
		}
		
		if(ans>6)printf("%d\n",-1);
		else printf("%d\n",ans);
	}

	return 0;
}

开关改变某行某列

ACwing 116 飞行员兄弟

给定初态4 * 4,问最少需要几步使所有灯变亮。

开关的连锁反应为:按下一个开关,该行该列所有开关都会改变。

题解:

只有16个格子,可将将每个格子编号,变成一个16个bit位的数,记为S。二进制枚举枚举16个格子是否按下,按下每个格子会改变该行该列格子的状态。可以先预处理出按下每个格子会改变的哪些编号的格子,根据格子的编号组成一个16bit位的数,S异或上这个数,就相当于改变了该行该列的状态。

#include <bits/stdc++.h>
using namespace std;
const int inf=0x3f3f3f3f;
int a[6][6];
char c;
int change[6][6];
int Q;
inline int getP(int &x,int &y){
	return x*4+y;
}

inline void getChange(int &x,int &y){
	//x,i
	//i,y
	for(int i=0;i<4;++i)change[x][y]^=(1<<getP(x,i)),change[x][y]^=(1<<getP(i,y));
	change[x][y]^=(1<<getP(x,y));
}
int main(){
	for(int i=0;i<4;++i){
		for(int j=0;j<4;++j)
			if( (c=getchar()) == '+')Q^=(1<<getP(i,j));
		getchar();
	}

//	cout<<Q<<endl;
//	cout<<(1<<1)+(1<<13)<<endl;


	for(int i=0;i<4;++i){
		for(int j=0;j<4;++j){
			getChange(i,j);
		//	cout<<i<<" "<<j<<" "<<change[i][j]<<endl;
		}
	}
	
	int mi=inf,cnt,ans;
	for(int i=0;i<1<<16;++i){
		int t=Q;cnt=0;
		for(int j=0;j<16;++j){
			if(i>>j & 1){
				++cnt;
				t^=change[j/4][j%4];
			}
		}
		
		if(!t){
			if(cnt<mi)ans=i,mi=cnt;
		}
	}
	
	printf("%d\n",mi);
	for(int i=0;i<16;++i)
		if(ans>>i & 1){
			printf("%d %d\n",i/4 + 1,i%4 + 1);
		}
	return 0;
}
posted @ 2020-07-14 10:42  yhsmer  阅读(203)  评论(0编辑  收藏  举报