小论“枚举”

       枚举搜索(Complete Search),即直接面向答案,通过尝试所有方案来发现答案,是我们解决一个问题时最先也是最容易想到的方法。应用枚举搜索编程时应该遵循”“KISS“原则,即”Keep it simple stupid”。因为枚举程序简单所以易于编写和调试,因为枚举的思想比较笨,所以运行比较耗时。应用枚举搜索需要特别注意能否在规定的时间内寻找出解,如果不行的话,思考搜索对象有无规律可寻,尝试利用枚举对象的特性来减少搜索的数据规模,然后再枚举搜索。


例如: POJ 1176 Party Lamps


Party Lamps
Time Limit: 1000MS Memory Limit: 10000K
Total Submissions: 4108 Accepted: 1409

Description

To brighten up the gala dinner of the IOI'98 we have a set of N coloured lamps numbered from 
1 to N. The lamps are connected to four buttons: 
button 1 -- when this button is pressed, all the lamps change their state: those that are ON are turned OFF and those that are OFF are turned ON. 
button 2 -- changes the state of all the odd numbered lamps. 
button 3 -- changes the state of all the even numbered lamps. 
button 4 -- changes the state of the lamps whose number is of the form 3K+1 (with K >= 0), i.e., 1,4,7,... 
There is a counter C which records the total number of button presses. 
When the party starts, all the lamps are ON and the counter C is set to zero. 

You are given the value of counter C and information on the final state of some of the lamps. Write a program to determine all the possible final configurations of the N lamps that are consistent with the given information, without repetitions.

Input

Your program is to read from standard input. The input contains four lines, describing the number N of lamps available, the number C of button presses, and the state of some of the lamps in the final configuration. 
The first line contains the number N and the second line the final value of counter C. The third line lists the lamp numbers you are informed to be ON in the final configuration, separated by one space and terminated by the integer -1. The fourth line lists the lamp numbers you are informed to be OFF in the final configuration, separated by one space and terminated by the integer -1. 

The parameters N and C are constrained by: 
10 <= N <= 100 
1 <= C <= 10000 
The number of lamps you are informed to be ON, in the final configuration, is less than or equal to 2.The number of lamps you are informed to be OFF, in the final configuration, is less than or equal to 2.

Output

Your program is to write to standard output. The output must contain all the possible final configurations (without repetitions) of all the lamps. There is at least one possible final configuration. Each possible configuration must be written on a different line. Each line has N characters, where the first character represents the state of lamp 1 and the last character represents the state of lamp N. A 0 (zero) stands for a lamp that is OFF, and a 1 (one) stands for a lamp that is ON. Configurations should be listed in binary ascending order.

Sample Input

10
1
-1
7 -1

Sample Output

0000000000
0101010101
0110110110

Source


题目中文描述 

                                                                              派对灯

在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'。

输入输出样例

Copy
10
1
-1
7 -1
Copy
0000000000
0101010101
0110110110

提示

在这个样例中,有10盏灯,只有1个按钮被按下。最后7号灯是关着的。

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

* 所有灯都关着

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

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


-----------------------------------------------------------------分割线------------------------------------------------------------------------------


题目告诉我们3种对象:

1. 4种按钮;

2.  N(10 < N < 100)盏有开着和关着两个状态的灯;

3. 范围为C(1 < C < 10000)的计数器。

如果分别对这3种不同的对象直接应用枚举搜索对应有三种方案:

A. 枚举4种按钮,即选择一个按钮按下,一共需要枚举C轮,时间复杂度为4^C

for buttonPressedOfTime1 = 1, 2, 3, 4
	changeLampsState(buttonPressedOfTime1);
	for buttonPressedOfTime2 = 1, 2, 3, 4
		changeLampsState(buttonPressedOfTime2)
		......
		for buttonPressedOfTimeC = 1, 2, 3, 4
			changeLampsState(buttonPressedOfTimeC)
			if lampsState match finalLampsState
				output lampsState

B. 枚举灯开着和关着两种状态,即确定灯是开着还是关着的,一共需要枚举N轮,时间复杂度为2^N;

for lamp1State = 0, 1
	for lamp2State = 0, 1
		......
			for lampNSate = 0, 1
				if lampsState match finalLampsState
					output lampsState

C. 枚举一个按钮被按下的次数,一共需要枚举4轮,时间复杂度为C^4;

for timesOfButton1Pressed = 1 to C
	changeLampsState(button1);
	for timesOfButton2Pressed = 1 to C
		changeLampsState(button2)
		for timesOfButton3Pressed = 1 to C
			changeLampsState(button3);
			for timesOfButton4Pressed = 1 to C
				changeLampsState(button4)
				if lampsState match finalLampsState
					output lampsState

3个方案的时间复杂度都很大,所以不能直接枚举搜索,观察枚举对象的规律

1. 灯的最后状态跟按钮的按下顺序无关;

2. 一个灯每按两次,效果抵消,相当于没按,所以按一个按钮多次可抵消为只按一次或者没按。

所以无论计数器C多大,最终可归为针对4个按钮,考察每个按钮按下与否,也就是2^4即16种情况进行考察,也就是对4个按钮的全组合进程考察。

另外还有一个重要的规律:

按钮2改变奇数的灯的状态,也就是2个灯一个循环,同理,按钮3改变偶数的灯的状态,也是两个灯一个循环,按钮3是改变1+3K的灯的状态,也就是3个灯一循环,我们取2和3的最大公倍数6,可得到6个灯一个循环,所我们只需要考察前六个灯的状态。

解题思路:

1. 因为我们只要考虑每个按钮按下与否两种状态,所以可以用一个二进制位代表一个按钮的状态,1代表按下,0代表没有按下,总共需要4个二进制位,然后直接通过位操作进行二进制枚举16种情况,具体代码可参考POJ原题讨论区戳这里

2. 也可以用一个二进制位代表一个灯的状态,1代表亮开着,0代表关着,总共只有16种亮灯状态需要考察,通过BFS枚举的方式有序打表,再通过查表的方式考察每种状态是否符合要求,符合即输出。

打表程序如下:

#include <stdio.h>

#define BUTTONS 4
#define SIZE_LOOP 6

struct State{
	int lamps;
	int counter;
};
State stackOfState[17];
int topOfState;
int operation[BUTTONS + 1] = {0, 0x3F, 0x2A, 0x15, 0x24};
int stackOfMinCounter[17];
int topOfMinCounter;

void pressOrNot(int preButton, int lamps, int counter){	
	int nextButton = preButton + 1;
	if (nextButton >  BUTTONS){
		State state;
		state.lamps = lamps;
		state.counter = counter;
		stackOfState[topOfState++] = state;
		return ;
	}
	pressOrNot(nextButton, lamps ^ operation[nextButton], counter + 1);
	pressOrNot(nextButton, lamps , counter);
}

void StateCopy(State *to, State *from){
	to->lamps = from ->lamps;
	to->counter = from->counter;
}

void swapState(State *one, State *another){
	State temp;
	StateCopy(&temp, one);
	StateCopy(one, another);
	StateCopy(another, &temp);
}

void sortStatesAccordingToLamps(){
	int time;
	for (time = 1; time < topOfState; time++){
		int needContinue = 0;
		int compare;
		for (compare = 0; compare < topOfState - 1; compare++){
			if (stackOfState[compare].lamps > stackOfState[compare + 1].lamps){
				needContinue = 1;
				swapState(&stackOfState[compare], &stackOfState[compare + 1]);
			}
		}
		if (needContinue == 0)
			break;
	}
}

void output(){
	printf("{ { },\n");
	int indexOfState;
	for (indexOfState = 0; indexOfState < topOfState; indexOfState++){
		int lamps = stackOfState[indexOfState].lamps;
		int shift;
		printf("{0, ");
		for (shift = SIZE_LOOP - 1; shift >= 0; shift--)
			printf("%d%s", ( (1 << shift) & lamps ) > 0 ? 1 : 0 , shift == 0 ? "}" : ", ");
		printf("%s", indexOfState == topOfState - 1 ? " }\n" : ",\n");
	}

	printf("\n{0, ");
	for (indexOfState = 0; indexOfState < topOfState; indexOfState++)
		printf("%d%s", stackOfState[indexOfState].counter, indexOfState == topOfState - 1 ? "}" : ", ");
}

int main(){
	freopen("output.txt", "w", stdout);
	int initialLamps = (1 << SIZE_LOOP) - 1;
	pressOrNot(0, initialLamps, 0);
	sortStatesAccordingToLamps();
	output();
	return 0;
}

注意:左边最高位代表灯1,右边最低位1位代表灯6,所以4个按钮对应的操作数分别是0x3F, 0x2A, 0x15, 0x24;


提交代码如下:
#include <stdio.h>

#define STATES 16
#define SIZE_LOOP 6

int arrayOfState[STATES + 1][SIZE_LOOP + 1] = 
											{ { },
											{0, 0, 0, 0, 0, 0, 0},
											{0, 0, 0, 0, 0, 0, 0},
											{0, 0, 0, 1, 1, 1, 0},
											{0, 0, 0, 1, 1, 1, 0},
											{0, 0, 1, 0, 1, 0, 1},
											{0, 0, 1, 0, 1, 0, 1},
											{0, 0, 1, 1, 0, 1, 1},
											{0, 0, 1, 1, 0, 1, 1},
											{0, 1, 0, 0, 1, 0, 0},
											{0, 1, 0, 0, 1, 0, 0},
											{0, 1, 0, 1, 0, 1, 0},
											{0, 1, 0, 1, 0, 1, 0},
											{0, 1, 1, 0, 0, 0, 1},
											{0, 1, 1, 0, 0, 0, 1},
											{0, 1, 1, 1, 1, 1, 1},
											{0, 1, 1, 1, 1, 1, 1} };
int minCounterOfState[STATES + 1] = {0, 1, 2, 3, 2, 2, 1, 4, 1, 2, 3, 2, 1, 3, 2, 3, 0};
int numOfLamps;
int counter;
int finalState[SIZE_LOOP + 1];

int transformLamp(int lamp){
	int lampInLoop = lamp % 6;
	if (lampInLoop == 0)
		lampInLoop = 6;		
	return lampInLoop;
}

int main(){
	scanf("%d %d", &numOfLamps, &counter);
	int lampInLoop;
	for (lampInLoop = 1; lampInLoop <= SIZE_LOOP; lampInLoop++)
		finalState[lampInLoop] = -1;
	int lamp;
	while (scanf("%d", &lamp) != EOF && lamp != -1){
		lampInLoop = transformLamp(lamp);
		finalState[lampInLoop] = 1;
	}
	while (scanf("%d", &lamp) != EOF && lamp != -1){
		lampInLoop = transformLamp(lamp);
		finalState[lampInLoop] = 0;
	}

	int indexOfState;
	for (indexOfState = 1; indexOfState <= STATES; indexOfState++){
		if (counter < minCounterOfState[indexOfState] || (1 & counter) != (1 & minCounterOfState[indexOfState]) )
			continue;

		int isMatch = 1;
		int lampInLoop;
		for (lampInLoop = 1; lampInLoop <= SIZE_LOOP; lampInLoop++){
			if (finalState[lampInLoop] != -1 && finalState[lampInLoop] != arrayOfState[indexOfState][lampInLoop]){
				isMatch = 0;
				break;
			}
		}
		if (isMatch == 0)
			continue;

		for (lamp = 1; lamp <= numOfLamps; lamp++){
			lampInLoop = transformLamp(lamp);
			printf("%d", arrayOfState[indexOfState][lampInLoop]);
		}
		printf("\n");
	}
	return 0;
}

注意:因为存在不同的按钮次数minCounterOfState对应相同的状态arrayOfState,所以要加上(1 & counter) != (1 & minCounterOfState[indexOfState])通过奇偶性对比来区分。


测试数据和输出:

15 8 4

001110001110001
010101010101010
100100100100100
111111111111111


10
0
-1
-1

1111111111


10
0
-1
1 -1



20
3
-1
1 3 5 -1

00000000000000000000
01010101010101010101


50
100
1 -1
-1

10010010010010010010010010010010010010010010010010
10101010101010101010101010101010101010101010101010
11000111000111000111000111000111000111000111000111
11111111111111111111111111111111111111111111111111


75
250
-1
-1

000000000000000000000000000000000000000000000000000000000000000000000000000
001110001110001110001110001110001110001110001110001110001110001110001110001
010101010101010101010101010101010101010101010101010101010101010101010101010
011011011011011011011011011011011011011011011011011011011011011011011011011
100100100100100100100100100100100100100100100100100100100100100100100100100
101010101010101010101010101010101010101010101010101010101010101010101010101
110001110001110001110001110001110001110001110001110001110001110001110001110
111111111111111111111111111111111111111111111111111111111111111111111111111


100
8394
1 7 13 19 25 31 37 43 49 55 -1
64 -1

1010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010
1100011100011100011100011100011100011100011100011100011100011100011100011100011100011100011100011100


100
2000
31 86 23 -1
42 -1



100
8950
-1
-1

0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
0011100011100011100011100011100011100011100011100011100011100011100011100011100011100011100011100011
0101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101
0110110110110110110110110110110110110110110110110110110110110110110110110110110110110110110110110110
1001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001
1010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010
1100011100011100011100011100011100011100011100011100011100011100011100011100011100011100011100011100
1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值