算法小谈:枚举

 一 概述

在进行归纳推理时,如果逐个考察了某类事件的所有可能情况,因而得出一般结论,那么这结论是可靠的,这种归纳方法叫做枚举法。

例如:我们如果要求一个小于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。

小结

枚举差不多就说到这里,下面的话开始递归的学习

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值