枚举算法
枚举算法(穷举法):按照问题本身的性质,一一列举该问题所有可能解,并在逐一列举的过程中,检验每个可能解是否是问题的真正解。
在列举的过程中不能遗漏也不能重复,遗漏会造成算法求解结果的不正确,重复会显著降低算法执行效率。
枚举算法的以下三个突出特点:
- 解的高准确性和全面性:
- 检验问题的每一种可能情况;
- (时间足够)枚举算法的答案肯定是正确的;
- 能方便求解出问题的所有解。
- 实现简单:通过循环
for
来逐一列举和验证。
- 效率提升空间大。
“枚举三步”:
- 确定枚举对象;
- 逐一列举可能解;
- 逐一验证可能解。
模糊数字
百位缺失
问题分析
1⃣确定枚举对象
在该五位数编码中,只有百位数模糊不清,百位数字只能是 0∼9 中的某一个数字。
⇒ 选择百位数作为枚举对象。
2⃣逐一列举可能解
将百位数字 h∈{0,1,2,3,4,5,6,7,8,9} 和问题中的其他已知 4 位数字组成完整的 5 位数编码 v 作为可能解。
3⃣逐一验证可能解
验证五位数 v 能否同时被 57 和 67 整除。
算法设计与实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| #include <iostream> #include <cstdio>
int main() { int iValue, d5, d4, d2, d1, h; int iResult[10]; scanf("%d %d %d %d", &d5, &d4, &d2, &d1); while (d5 != -1) { int iCount = 0; for (int h = 0; h < 10; h++) { iValue = 10000 * d5 + 1000 * d4 + 100 * h + 10 * d2 + d1; if ((iValue % 57 == 0) && (iValue % 67 == 0)) iResult[iCount++] = iValue; } printf("%d", iCount); for (int i = 0; i < iCount; i++) printf(" %d", iResult[i]); printf("\r\n"); scanf("%d %d %d %d", &d5, &d4, &d2, &d1); } return 0; }
|
万位和百位缺失
1⃣确定枚举对象
在该五位数编码中,万位数字和百位数字都模糊不清,这两位数字只能是 0∼9 中的某一个数字。
⇒ 选择万位数字和百位数字作为枚举对象。
2⃣逐一列举可能解
- 万位数字 h1∈{0,1,2,3,4,5,6,7,8,9}
- 百位数字 h2∈{0,1,2,3,4,5,6,7,8,9}
- 问题中的其他已知3位数字
和问题中的其他已知 4 位数字组成完整的 5 位数编码 v 作为可能解。
3⃣逐一验证可能解
验证五位数 v 能否同时被 57 和 67 整除。
算法设计与实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| #include <iostream> #include <cstdio>
int main() { int iValue, d5, d4, d2, d1, h; int iResult[10]; scanf("%d %d %d %d", &d5, &d4, &d2, &d1); while (d5 != -1) { int iCount = 0; for (int h1 = 0; h1 < 10; h1++) { for (int h2 = 0; h2 < 10; h2++) iValue = 10000 * h1 + 1000 * d4 + 100 * h2 + 10 * d2 + d1; if ((iValue % 57 == 0) && (iValue % 67 == 0)) iResult[iCount++] = iValue; } printf("%d", iCount); for (int i = 0; i < iCount; i++) printf(" %d", iResult[i]); printf("\r\n"); scanf("%d %d %d %d", &d5, &d4, &d2, &d1); } return 0; }
|
m 钱买 n 鸡
问题分析
1⃣确定枚举对象
任何一个购买方案都会包含鸡翁、鸡母、鸡雏的数目 n1、n2、n3,且任意一种类型的鸡数目不能超过 n。
⇒ 枚举对象用三元组 (n1,n2,n3),且 0⩽n1,n2,n3⩽n 表示
2⃣逐一列举可能解
0⩽n1,n2,n3⩽n
3⃣逐一验证可能解
n1+n2+n3=n 且 5n1+3n2+3n3=m
算法设计与实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| #include <iostream> #include <cstdio>
int main() { int N, M, n1, n2, n3; scanf("%d %d", &N, &M); while (N != -1) { int iCount = 0; for (n1 = 0; n1 <= N; n1++) for (n2 = 0; n2 <= N; n2++) for (n3 = 0; n3 <= N; n3++) if ((n1 + n2 + n3 == N) && (5 * n1 + 3 * n2 + n3 / 3.0 == M)) iCount++; printf("%d", iCount); printf("\r\n"); scanf("%d %d", &N, &M); } return 0; }
|
上面代码时间复杂度 O(n3),当输入的规模增大,时间也会增大,进一步缩小范围:
- 购买鸡翁的数目不可能超过 m/5,0⩽n1⩽m/5
- 购买鸡母的数目不可能超过 m/3,0⩽n2⩽m/3
- 当鸡翁和鸡母的数目确定之后,鸡雏的数目也唯一确定,n3=n−n1−n2
算法时间复杂度减少为O(n2)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| #include <iostream> #include <cstdio>
int main() { int N, M, n1, n2, n3; scanf("%d %d", &N, &M); while (N != -1) { int iCount = 0; for (n1 = 0; n1 <= M / 5; n1++) for (n2 = 0; n2 <= M / 3; n2++) { n3 = N - n1 - n2; if (5 * n1 + 3 * n2 + n3 / 3.0 == M) iCount++; } printf("%d", iCount); printf("\r\n"); scanf("%d %d", &N, &M); } return 0; }
|
真假银钱
问题分析
1⃣确定枚举对象
从三组数据中得到唯一答案,答案的两个要素:
- 假币标号 s :s∈{A,B,⋯,L}
- 假币比真币轻还是重 b:b∈{light,heavy,true}
- light:假币比真币轻;
- heavy:假币比真币重;
- true:银币为真。
⇒ 枚举对象用二元组 (s,b) 表示
2⃣逐一列举可能解
3⃣逐一验证可能解
与输入的 3 组称重数据进行比对,存在一组或一组以上的矛盾,不是问题的解。
如果都不矛盾,是问题的真正解,更具体的:
- even 情况下,两边的天平都没有出现 s;
- 如果 b=light:
- 称量结果为 up 的天平右边出现 s
- 称量结果为 down 的天平左边出现 s
- 如果 b=heavy:
- 称量结果为 up 的天平左边出现 s
- 称量结果为 down 的天平右边出现 s
算法设计与实现
选择合适的算法
- s 比真币轻的猜测是否成立?猜测成立则进行输出;
- s 比真币重的猜测是否成立?猜测成立则进行输出;
- 如果上述两种猜测都不成立,则表明 s 是真币,继续验证下一个银币。
选择合适的数据结构
- 用字符串数组存储每次称量的结果
- 天平左右每次最多有 6 枚银币 ⇒ 字符串长度最多为 7(6+1=7)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
| #include <iostream> #include <cstring>
char left[3][7], right[3][7], result[3][5];
bool isLight(char); bool isHeavy(char);
bool isLight(char curChar) {
for (int i = 0; i < 3; i++) { switch (result[i][0]) { case 'u': if (strchr(right[i], curChar) == NULL) return false; break; case 'e': if ((strchr(right[i], curChar) != NULL) || (strchr(left[i], curChar) != NULL)) return false; break; case 'd': if (strchr(left[i], curChar) == NULL) return false; break; } } return true; }
bool isHeavy(char curChar) { for (int i = 0; i < 3; i++) { switch (result[i][0]) { case 'u': if (strchr(left[i], curChar) == NULL) return false; break; case 'e': if ((strchr(right[i], curChar) != NULL) || (strchr(left[i], curChar) != NULL)) return false; break; case 'd': if (strchr(right[i], curChar) == NULL) return false; break; } } return true; }
int main() { int n; scanf("%d", &n); while (n--) { for (int i = 0; i < 3; i++) scanf("%s %s %s", left[i], right[i], result[i]); for (char curChar = 'A'; curChar <= 'L'; curChar++) { if (isLight(curChar)) { printf("%c %s", curChar, "light"); printf("\r\n"); break; } if (isHeavy(curChar)) { printf("%c %s", curChar, "heavy"); printf("\r\n"); break; } } } return 0; }
|