一 概述
在进行归纳推理时,如果逐个考察了某类事件的所有可能情况,因而得出一般结论,那么这结论是可靠的,这种归纳方法叫做枚举法。
例如:我们如果要求一个小于N的最大素数,但是这里没有一个公式能根据N求出对应的素数,所以我们可以从N-1,N-2 一直往下递减,然后判断这个数是不是N的素数,直到找到为止。这样我们就把这个问题转化为一个枚举问题了。
二 完美立方等式
我们可以通过一个简单的例子来更清楚的认识这个问题,如下所示:
要求:请按照a的值,从大到小依次输出。如果两个完美立方等式中的a值大小相同时,则b值小的先输出,如果仍相同,则c值小的先输出,再相同则d值小的先输出。如果输入正整数N为24,则应该输出:
我们可以设置四重循环,a在最外面,d在最里面,然后依次枚举。具体程序如下所示:
#include<iostream>
using namespace std;
//函数说明:完美立方公式
int main() {
int N;
cin >> N;
for (int a = 2; a <= N; a++) {
for (int b = 2; b <= a; b++) {
for (int c = b; c <= a; c++) {
for (int d = c; d <= a; d++) {
if (a*a*a == b*b*b + c*c*c + d*d*d) {
cout << "Cube = " << a <<" "<< "Triple = " << (b, c, d)<< endl;
//printf("Cube = %d,Triple = (%d,%d,%d)\n", a, b, c, d);
}
}
}
}
}
system("pause");
return 0;
}
好啦,我们编译程序之后就可以看到对应输出N的完美立方公式的参数啦,假如我们输入N 值为37,则结果为:
接下里,我们将研究称硬币问题。
三:称硬币问题
解题思路:我们可以枚举每一个硬币,假设他是轻的,然后代入输入样例中,观察是否满足结果,如果不满足,那么就假设该硬币是重的,继续代入,看是否满足结果,都不满足就继续循环,把所有硬币全试一遍,最后找到假硬币。程序代码如下:
#include<iostream>
#include<cstring>
using namespace std;
//定义全局变量
char Left[3][6]; //放置天平左边的硬币,因为总共有12个硬币,所以每一次称量左边最多放置6个
char Right[3][6]; //放置天平右边的硬币
char Result[3]; //称量的结果
bool isFake(char c, bool light) { //light为true则假设假币为轻,false假设假币为重
char *pLeft, *pRight;
for (int i = 0; i < 3; i++) {
//轻重硬币只要改变指针指向
if (light) {
pLeft = Left[i], pRight = Right[i];
}
else {
pRight = Left[i], pLeft = Right[i];
}
// 判断是否符合结果
switch (Result[i]) {
case 'u':
if (strchr(pRight, c) == NULL) {
return false;
}
break;
case 'e':
if (strchr(pRight, c) || strchr(pLeft, c)) {
return false;
}
break;
case 'd':
if (strchr(pRight, c) == NULL) {
return false;
}
break;
}
}
return true; //全部符合结果则返回true
}
int main(){
int t;
cin >> t; //实验次数
while (t--) {
for (int i = 0; i < 3; i++) {
cin >> Left[i] >> Right[i] >> Result[i]; //输入该次实验左右天平的硬币以及结果
}
for (char c = 'A'; c <= 'L'; c++) {
if (isFake(c, true)) {
cout << c << "是假硬币并且重量为轻";
break;
}
else if (isFake(c, false)) {
cout << c << "是假硬币并且重量为重";
break;
}
}
}
system("pause");
return 0;
}
运行程序,输入测试样例,结果如下:
最后我们再来看一个比较难理解的问题,天黑请闭眼:D
四:熄灯问题
输入为灯的初始状态:0表示灯是熄灭的,1表示灯饰点亮的
输出为使灯全灭的开关方案:0表示不需要按对应的按钮,1表示要把按钮按下
样例输入输出如下所示:
2代表灯初始状态的个数,PUZZLE #1代表对应第一种灯初始状态是的灯全灭的开关方案,PUZZLE #2代表对应第二种灯初始状态是的灯全灭的开关方案。
分析:1、按钮按下两次等于没按,按下三次等于按一次,所以每个按钮最多只需要按下一次;2、假如第一行中存在点亮的灯,那么只需要按下第二行中的对应的按钮,继续重复第三行,第四行,第五行就行。
如果我们枚举所有的开关状态,因为每个开关有两种状态,总共有30个按钮,那么就有2的30次方中不同的状态,程序会超时,所以我们可以换一种思考方式。正如上面所说的,在第一行的灯状态确定的情况下,我们按下某些灯,会使得一部分亮,一部分暗,共有2的6次方个,共64中开关方案,那么在第二行我们就不需要枚举每一种状态,只需要按下第一行中某个亮着的灯(假设位于第i列)对应第二行第i列的灯就行了。同理,第二行的开关方案确定后,继续如法炮制,确定第三行的开关方案,以此类推。最后判断最后一行是不是全灭,是的话,那么返回该开关方案,不是就继续循环。
因为每一行只有6个数据,且都是0,1的数,因此我们可以用一个二进制字符串来代表每一行的开关状态。程序代码如下所示:
#include<iostream>
#include<string>
#include<string>
#include<memory>
using namespace std;
int GetBit(char c, int i) {
return (c >> i) & 1; //右移i位与1(000001)进行与运算,得到字符c第i位bite的状态
}
void setBit(char &c, int i, int v) {
if (v) {
c |= (1 << i); //把c的第i位设置为1
}
else {
c &= ~(1 << i); //把c的第i位设置为0
}
}
void flipBit(char &c, int i) {
c ^= (1 << i); //按位运算:与1异或相反 与0异或不变,对字符c的第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() {
char oriLight[5]; //灯的初始状态
char Light[5]; //变化过程中的灯的状态
char ResultLight[5]; //结果开关矩阵
char switches; //某一行的开关矩阵
int T;
cin >> T; //几组灯初始状态的数据
for (int t = 1; t <= T; t++) {
//初始化灯矩阵
memset(oriLight, 0, sizeof(oriLight));
//读入灯的初始状态
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 6; j++) {
int s;
cin >> s;
setBit(oriLight[i], j, s);
}
}
//第一行的开关方案共有64种
for (int k = 0; k < 64; k++) {
memcpy(Light, oriLight, sizeof(oriLight)); //初始化Light矩阵
switches = k; //第一行的开关方案
for (int i = 0; i < 5; i++) {
ResultLight[i] = switches;//把第i行的开关方案放入结果矩阵
for (int j = 0; j < 6; j++) {
if (GetBit(switches, j)) {
if (j > 0) {
flipBit(Light[i], j - 1); //不是第一列时,翻转左边的灯
}
flipBit(Light[i], j); //翻转当前位置的灯
if (j < 5) {
flipBit(Light[i], j + 1); //不是第一列时,翻转右边的灯
}
}
}
if (i < 4) {
Light[i + 1] ^= switches; //改第i+1行的灯状态,按位异或
}
switches = Light[i]; //第i行的灯状态,就是第i+1行的开关方案
}
if (Light[4] == 0) {
outputResult(t, ResultLight);
break;
}
}
}
system("pause");
return 0;
}
执行程序测试我们的样例,偷个懒我只测试了第一种情况,如下所示:
看,是不是和提供的样例输出一样:D。
小结
枚举差不多就说到这里,下面的话开始递归的学习