USACO:2.2.4 Party Lamps 派对灯
一、题目描述
★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)上的数值和经过若干操作后某些灯的状态. 写一个程序去找出所有的灯最后可能的与所给出信息相符的状态,并且没有重复.
PROGRAM NAME: lamps
INPUT FORMAT
不会有灯会在输入中出现两次.
第一行: N.
第二行: C 最后显示的数值.
第三行: 最后亮着的灯,用一个空格分开,以-1 为结束.
第四行: 最后关着的灯,用一个空格分开,以-1 为结束.
SAMPLE INPUT (file lamps.in)
10
1
-1
7 -1
在这个样例中,有10 盏灯,只有1 个按钮被按下.最后7 号灯是关着的.
OUTPUT FORMAT
每一行是所有灯可能的最后状态(没有重复).每一行有N 个字符,第1 个字符表示1 号灯,最后一个
字符表示N 号灯.0 表示关闭,1 表示亮着.这些行必须从小到大排列(看作是二进制数).
如果没有可能的状态,则输出一行'IMPOSSIBLE'.
SAMPLE OUTPUT (file lamps.out)
0000000000
0101010101
0110110110
在这个样例中,有三种可能的状态:
所有灯都关着1,4,7,10 号灯关着,2,3,5,6,8,9 亮着.
1,3,5,7,9 号灯关着,2, 4, 6, 8, 10 亮着.
二、题目解答
每个开关按钮按2次和没按效果是一样的。所以每个按钮或者按或者不按,一共有2^4=16中开关状态。枚举每个按钮是否按下,然后生成结果,看是否满足条件,排序输出即可(注意判重)。
另外,根据四种开关的操作效果,灯状态的周期为6,因此当N>=6时只需处理前6个灯, 排序时转换为10进制数, 输出时反复输出二进制的前6个的状态.
源代码
#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <map>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iomanip>
#include <algorithm>
using namespace std;
int N,C;
vector<int> final_on,final_off;
string s1;
map<int,bool> mymap;//第一个为key,int类型,必须唯一。
int state6[16];//利用N个灯的周期性,只要考虑前6个灯即可;最多16种状态
int check(string s){
for(int i=0;i<final_on.size();i++)
{if(s[(final_on[i]-1)%6]=='0') return 0;}
for( int i=0;i<final_off.size();i++)
{if(s[(final_off[i]-1)%6]=='1')return 0;}
return 1;
}
int main()
{
ifstream fin ("lamps.in");
ofstream out("lamps.out");
fin>>N;
fin>>C;
for(int i=0;fin>>i&&i!=-1;i++)
final_on.push_back(i);
for(int i=0;fin>>i&&i!=-1;i++)
final_off.push_back(i);
//从开关的状态对应前6个灯的状态,最高位为第一个开关
//关于状态的记录,用一个int存储N个灯的前6位的状态就行了。
//状态的改变,通过开关,4种操作也很简单,用异或就可以搞定:
//操作1:异或(111111)=63 二进制数 等价于取反
//操作2:异或(010101)=21 改变偶数位
//操作3:异或(101010)=42 改变奇数位
//操作4:异或(100100)=36 改变1、4、7位
int j=0;
int c=C%2;
int newstate;
if(c==0){
newstate=~0;
if(mymap[newstate]==0) {state6[j++]=newstate;mymap[newstate]=1;}//操作1
if(C>1){
//****0011 0101 0110 1001 1010 1100 状态3,5,6,9,10,12;
//只有两个开关为打开状态,C可能为2+2的幂次
//0011
newstate=~42^36;
if(mymap[newstate]==0) { state6[j++]=newstate; mymap[newstate]=1;}//
//0101
newstate=~36^21;
if(mymap[newstate]==0) { state6[j++]=newstate; mymap[newstate]=1;}//
//0110
newstate=~42^21;
if(mymap[newstate]==0) { state6[j++]=newstate; mymap[newstate]=1;}//
//1001
newstate=~36^63;
if(mymap[newstate]==0) { state6[j++]=newstate; mymap[newstate]=1;}//
//1010
newstate=~42^63;
if(mymap[newstate]==0) { state6[j++]=newstate; mymap[newstate]=1;}//
//1100
newstate=~21^63;
if(mymap[newstate]==0) { state6[j++]=newstate; mymap[newstate]=1;}//
//****1111 状态15;C可能为4+2的幂次
if(C>=4){
newstate=~21^63^36^42;
if(mymap[newstate]==0) { state6[j++]=newstate; mymap[newstate]=1;}//
}
}
}
else {//C为奇数
//****0001 0010 0100 1000 状态1,2,4,8;
//只有一个开关为打开状态,C可能为1+2的幂次,奇数
//0001
newstate=~36;
if(mymap[newstate]==0) { state6[j++]=newstate; mymap[newstate]=1;}//
//0010
newstate=~42;
if(mymap[newstate]==0) { state6[j++]=newstate; mymap[newstate]=1;}//
//0100
newstate=~21;
if(mymap[newstate]==0) { state6[j++]=newstate; mymap[newstate]=1;}//
//1000
newstate=~63;
if(mymap[newstate]==0) { state6[j++]=newstate; mymap[newstate]=1;}//
//****0111 1101 1011 1110 状态7,13,11,14;可能为3+2的幂次
if(C>2){
//0111
newstate=~21^42^36;
if(mymap[newstate]==0) { state6[j++]=newstate; mymap[newstate]=1;}//
//1101
newstate=~63^21^36;
if(mymap[newstate]==0) { state6[j++]=newstate; mymap[newstate]=1;}//
//1011
newstate=~63^42^36;
if(mymap[newstate]==0) { state6[j++]=newstate; mymap[newstate]=1;}//
//1110
newstate=~63^42^21;
if(mymap[newstate]==0) { state6[j++]=newstate; mymap[newstate]=1;}//
}
}
//16种开关状态 (对应4种开关的状态)
//0000 状态0;C可能为0+2的幂次(等效按开关0次)
//1111 状态15;C可能为4+2的幂次(等效按开关4次)
//0011 0101 0110 1001 1010 1100 状态3,5,6,9,10,12;C可能为2+2的幂次(等效按开关2次)
//0001 0010 0100 1000 状态1,2,4,8;C可能为1+2的幂次,奇数(等效按开关1次)
//0111 1101 1011 1110 状态7,13,11,14;可能为3+2的幂次(等效按开关3次)
sort(state6,state6+j);//
int flag=0;
for( int i=0;i<j;i++){
int decimal = state6[i];;
string s,ss;
for(int j=5;j>-1;j--){
if((decimal>>j)&1) ss="1";//整数化为二进制的字符串
else ss="0";
s=s+ss;
}
if(check(s)){
for(int i=0;i<N/6;i++) out<<s;
string sTemp=s.substr(0,N%6);
out<<sTemp<<endl;
flag=1;
}
}
if(!flag) {out<<"IMPOSSIBLE"<<endl;}
return 0;
}
官方答案
//There are a number of insights required for this problem.
//The main insight is that no matter which switches get pressed, the light pattern will repeat every six lamps.
//Another insight is that the order of switch presses does not matter,
//and that pressing a switch twice is the same as not pressing a switch at all.
//Any even number of switch presses greater than four might as well just be four switch presses,
//and any odd number greater than three might as well just be three presses.
//We can treat the light information as describing only the first six lamps (by treating lamp n as lamp (n mod 6)),
//and only try pressing each switch once or not at all.
//To maintain the actual information about which lights are on, we use an integer holding six bits.
//The least significant bit is light 6, the next least is light 5, and so on.
//This has the effect that treating these bit strings as normal numbers sorts the same way that we need to print them.
//Switches are represented as bit vectors that say which lights get toggled.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#define MAXLAMP 6 //定义最大灯数 6,因为灯的状态具有循环特性。
#define LAMPMASK ((1<<MAXLAMP)-1)//低六位为111 111
int nlamp;
int nswitch;
int ison;
int known;
int poss[1<<MAXLAMP];//1左移6位
int flip[4] = {//翻转
LAMPMASK, // flip all lights */低6位 111 111
LAMPMASK & 0xAA, //flip odd lights */ 低6位 101 010
LAMPMASK & 0x55, //flip even lights */低6位 010 101
LAMPMASK & ((1<<(MAXLAMP-1))|(1<<(MAXLAMP-4))) //lights 1, 4 低6位100 100
};
Starting with current light state `lights', flip exactly n switches
with number >= i.
void
search(int lights, int i, int n)
{
if(n == 0) {
if((lights & known) == ison)
poss[lights] = 1; //
return;
}
for(; i<4; i++) //开关操作递增,不会重复。每个类型操作,最多操作一次(因为重复没有意义,是指操作总数化简后)
search(lights ^ flip[i], i+1, n-1); //异或,相当于一次开关操作
}
void
printseq(FILE *fout, int lights)//输出结果
{
int i;
char s[100+1];
for(i=0; i<nlamp; i++)
s[i] = (lights & (1<<(MAXLAMP-1 - i%MAXLAMP))) ? '1' : '0';
s[nlamp] = '\0';
fprintf(fout, "%s\n", s);
}
int
main(void)
{
FILE *fin, *fout;
int a, i, impossible;
fin = fopen("lamps.in", "r");
fout = fopen("lamps.out", "w");
assert(fin != NULL && fout != NULL);
fscanf(fin, "%d %d", &nlamp, &nswitch);
for(;;) {
fscanf(fin, "%d", &a);
if(a == -1)
break;
a = MAXLAMP-1 - (a-1) % MAXLAMP;
ison |= 1<<a; //或,将a中最后状态为on的灯存储为1
known |= 1<<a;//或,know中存储状态已知的灯的位置
}
for(;;) {
fscanf(fin, "%d", &a);
if(a == -1)
break;
a = MAXLAMP-1 - (a-1) % MAXLAMP;
assert((ison & (1<<a)) == 0);//或,是否出现已知状态间自相矛盾
known |= 1<<a;//或,know中存储状态已知的灯的位置
}
因为4个开关,每个只有两种状态0/1,化简化后最多4次开关操作。//可以分为奇数和偶数两种情况
if(nswitch > 4){
if(nswitch%2 == 0)
nswitch = 4;//偶数情况,4/2/0
else
nswitch = 3;//奇数情况,3/1
}
for(; nswitch >= 0; nswitch -= 2)//搜索对应情况
search(LAMPMASK, 0, nswitch);//LAMPMASK灯的原始状态,0代表第一种开关操作,nswitch代表最多几次开关操作
impossible = 1;
for(i=0; i<(1<<MAXLAMP); i++) {
if(poss[i]) { //从小到大搜索,所以输出的时候肯定是按从小到大的顺序
printseq(fout, i);
impossible = 0;
}
}
if(impossible)
fprintf(fout, "IMPOSSIBLE\n");
exit(0);
}
由于自身是初学者,编程能力有限,未达到专业程序员的水平,可能误导大家,请大家甄读;文字编辑也一般,文中可能会有措辞不当。博文中的错误和不足敬请读者批评指正。
部分引自 http://pingce.ayyz.cn:9000/usaco/20110129214306/lamps_001.html