【USACO2.2.4】Party Lamps派对灯——杨子曰题目

【USACO2.2.4】Party Lamps派对灯——杨子曰题目

题目描述
在IOI98的节日宴会上,我们有N(10<=N<=100)盏彩色灯,他们分别从1到N被标上号码。 这些灯都连接到四个按钮:

按钮1:当按下此按钮,将改变所有的灯:本来亮着的灯就熄灭,本来是关着的灯被点亮。

按钮2:当按下此按钮,将改变所有奇数号的灯。

按钮3:当按下此按钮,将改变所有偶数号的灯。

按钮4:当按下此按钮,将改变所有序号是3*K+1(K>=0)的灯。例如:1,4,7…

一个计数器C记录按钮被按下的次数。当宴会开始,所有的灯都亮着,此时计数器C为0。

你将得到计数器C(0<=C<=10000)上的数值和经过若干操作后某些灯的状态。写一个程序去找出所有灯最后可能的与所给出信息相符的状态,并且没有重复。

输入输出格式
输入格式:
不会有灯会在输入中出现两次。

第一行: N。

第二行: C最后显示的数值。

第三行: 最后亮着的灯,用一个空格分开,以-1为结束。

第四行: 最后关着的灯,用一个空格分开,以-1为结束。

输出格式:
每一行是所有灯可能的最后状态(没有重复)。每一行有N个字符,第1个字符表示1号灯,最后一个字符表示N号灯。0表示关闭,1表示亮着。这些行必须从小到大排列(看作是二进制数)。

如果没有可能的状态,则输出一行’IMPOSSIBLE’。

输入样例

10
1
-1
7 -1

输出样例#1:

0000000000
0101010101
0110110110

说明
在这个样例中,有三种可能的状态:

所有灯都关着

1,4,7,10号灯关着,2,3,5,6,8,9亮着。

1,3,5,7,9号灯关着,2, 4, 6, 8, 10亮着。

翻译来自NOCOW

(翻译复制自洛谷:https://www.luogu.org/problemnew/show/P1468)


“真是一道有趣的半打表题,果断拿出来share一下”
“什么!这是打表题吗?我们怎么觉得像深搜捏?”
“大哥,算算复杂度行不——O(4^C)
“那我剪剪枝”
“你加油慢慢剪,我去讲O(n)的算法了”

正解走起:


我们来看这四个操作,你会发现有点玄妙:
按钮1:隔0个取反
按钮2:隔1个取反
按钮3:隔1个取反
按钮4:隔2个取反

于是乎,用你灵敏的嗅觉一定察觉到了什么:不管怎么操作,整列灯必定是由循环节构成,而且循环节的的长度就是6
欧,也就是说(N<=100)这个条件就是骗骗你的,我们真正需要考虑的长度只有6!

然后捏?

然后你就会发现最后的状态也就只有2^6也就是64种,但是这64种真的种种都可以做到吗?比方说,试试这个:100001,你会发现再怎么操作也操作不出100001,那么能操作出的有几种呢?

手模ing~~~

重点是模拟了半天不知道全没全!于是就打个代码呗(做一道题居然要打两个代码(・∀・(・∀・(・∀・*)):
(我猜绝对有人打开博客,前面的内容完全没看,往下划划划,呦!代码!美滋滋!赶紧复制,提交。( ఠൠఠ )WA了,杨子曰骗人!)

#include<bits/stdc++.h>
using namespace std;

int a[7],rec[100][7],num=0; 

void init(){
	for (int i=1;i<=6;i++){
		a[i]=1;
	}
}

int check(){
	for (int i=1;i<=num;i++){
		int flag=1;
		for (int j=1;j<=6;j++){
			if (a[j]!=rec[i][j]) flag=0;
		} 
		if (flag) return 0;
	}
	return 1;
}

void dfs(int x){
	int t[10];
	for (int i=1;i<=6;i++){
		t[i]=a[i];
	}
	if (x==1) for (int i=1;i<=6;i++) a[i]=!a[i];
	else if (x==2) a[1]=!a[1],a[3]=!a[3],a[5]=!a[5];
	else if (x==3) a[2]=!a[2],a[4]=!a[4],a[6]=!a[6];
	else if (x==4) a[1]=!a[1],a[4]=!a[4];
	if (check()){
		++num;
		for (int i=1;i<=6;i++){
			rec[num][i]=a[i];
		}
	}
	else return;
	for (int i=1;i<=6;i++){
		a[i]=t[i];
	}
	for (int i=1;i<=4;i++){
		if (i==x) continue;
		dfs(i);
	}
}

void getrec(){
	for (int i=1;i<=4;i++){
		init();
		dfs(i);
	}
}

int main(){
	getrec();
	cout<<num<<endl;
	for (int i=1;i<=num;i++){
		for (int j=1;j<=6;j++){
			cout<<rec[i][j];
		}
		cout<<endl;
	}
	return 0;
}

然后看一下num和rec,哟!只有8个,不错不错,它们分别是(这里已经用人脑把它们的顺序给排好了):

000000
001110
010101
011011
100100
101010
110001
111111

但是,在做完规定的步数c后,依然有一些状态是达不到的,比如就让你动一次,你试着把111111变成110001试试?IMPOSSIBLE!这个故事告诉我们,我们还要把达到这个状态所需要的最少步数算一算

手模ing~~~(怎么总感觉在做数学题)

于是我们得到了:

000000  最少1001110  最少2010101  最少1011011  最少1100100  最少2101010  最少1110001  最少2111111  最少0

肯定还有一些机智的大佬依然认为:即使我的步数是足够的,也就是大于了当前状态所需的最少步骤数,依然不一定能达到目标,还要判断奇偶性的问题

比如我用奇数步可以到010101,花偶数步可以吗!(弱弱地说:哦,好像可以,先按1,再按3;强强地说:但所有情况都可以吗!

(顺便提一下,如果一个状态奇数次操作可以完成的话,任意奇数次操作都可以,我们只要不停地连续做两次两次两次……的按钮1就行了,偶数也是一样的)

你的想法很有道理,BUT,我们只要考虑特例就行了。我们先来想这样一件事情:是不是如果我能把一个一步的操作换成两个操作而达到同样的效果,那么对于这个操作而言它的奇偶性就可以随意转化,比方说上面那个例子按钮2,就可以被分解成按钮1+按钮3

于是我们来把所有的按钮都给分解了:

按钮1=按钮2+按钮3
按钮2=按钮1+按钮3
按钮3=按钮1+按钮2
按钮4=按钮1+按钮2+按钮3+按钮4

有没有发现按钮4是个奇葩,一变就得变四个,不能变两个,So,对于按钮4要变两个才能达到要求的情况我们是要特判的

哦!别忘了还有一种奇葩状态111111,因为它的最少步骤是0,So,按一次按钮是出不来这种情况的,也得特判

这样一来我们就找齐了所有特例(出现以下状况都是办不到的):

步骤次数达不成的状态
2011011
1111111

于是剩下的就是可以达到的了

这样一来,我们在能达到的最终状态里找到满足题目要求的最终开关灯情况的输出就欧了

提一下:由于6个一循环,我们把题目中给的编号模6以后来处理就行了

OK,完事


c++代码:

#include<bits/stdc++.h>
using namespace std;

int a[10],flag[10],num=0,cnt=0,n,c;

int rec[9][7]={
0,0,0,0,0,0,0,
0,0,0,0,0,0,0,
0,0,0,1,1,1,0,
0,0,1,0,1,0,1,
0,0,1,1,0,1,1,
0,1,0,0,1,0,0,
0,1,0,1,0,1,0,
0,1,1,0,0,0,1,
0,1,1,1,1,1,1,
};//从坐标横坐标各有一个0维,我直接把它用0垫掉了,当然你可以从0开始存

int s[9]={0,1,2,1,1,2,1,2,0};

int f(int x){
	if (x%6==0) return 6;
	return x%6;
}//对于6的倍数要特殊判断,当然你可以从0开始存

int check(int x){
	if (c==2 && x==4) return 0;
	if (c==1 && x==8) return 0;//特殊情况
	if (s[x]>c) return 0;//步数不够
	for (int i=1;i<=6;i++){
		if (flag[i]==-1) continue;
		if (rec[x][i]!=flag[i]) return 0;
	}
	return 1;
}

void print(int x){
	for (int i=1;i<=n;i++){
		printf("%d",rec[x][f(i)]);
	}
	cout<<endl;
}

int main(){
	memset(flag,-1,sizeof(flag));
	scanf("%d%d",&n,&c);
	int k;
	while(scanf("%d",&k) && k!=-1){
		flag[f(k)]=1;
	}
	while(scanf("%d",&k) && k!=-1){
		if (flag[f(k)]==1){
			printf("IMPOSSIBLE");
			return 0;
		}
		flag[f(k)]=0;
	}
	int b=0;
	for (int i=1;i<=8;i++){
		if (check(i)) print(i),b=1;
	}
	if (!b) printf("IMPOSSIBLE");
	return 0;
} 

于HG机房

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值