谁在说谎
考虑这样一些陈述:
西电有四位同学中的一位做了好事,分别询问,他们的回答分别是:
- A说:不是我
- B说:是C
- C说:是D
- D说:C胡说
已知四人中三人说了真话,一人说的是假话,请问谁说了谎话,谁做的好事。
通过简单的规约我们即可获得答案,B和C有一人说谎,A和D自然说的都是实话,D说:C胡说,则C说慌,C是做好事的人。
我们如何交由计算机实现呢?枚举法,所谓枚举,也即需要在全部可能的空间中进行遍历判断。
#include <iostream>
int main(int, char**)
{
char space[] = {'A', 'B', 'C', 'D'};
// 重要!整个解空间的定义
int i = 0;
for(; i < 4; i++)
{
int n_honest = (space[i] != 'A') + (space[i] == 'C') + (space[i] == 'D') + !(space[i] == D);
if (n_honest == 3)
break;
}
std::cout << space[i] << " did the good things" << std::endl;
return 0;
}
谁是犯罪嫌疑人
某地刑侦大队对涉及6个嫌疑人的一桩疑案作如下分析:
- A,B至少有一人作案
- A, E, F 3人中至少有两人参与作案
- A, D不可能是同案凡
- B, C或同时作案,或与本案无关
- C,D中有且仅有一人作案
- 如果D没有作案,则E也不可能参与作案
这种情况下,可能性较多,人工的方式可能比较费时,如果能充分利用现代计算机的计算能力(辣么高的主频),将会给类似问题带来新的契机。
仍然采用枚举的方式,对整个可能空间进行遍历式判断。已知一共六个人,每个人都只有“是罪犯”“不是罪犯”两种可能,则6个人组成的可能数 26=64 :
#include <iostream>
#include <bitset>
int main(int, char**)
{
bool b1, b2, b3, b4, b5, b6;
char judge[2][9] = {"不是罪犯", "是罪犯"};
// 中文占两个字节
for (int i = 0; i < pow(2, 6); ++i)
{
std::bitset<6> bs(i);
// i = 0 ⇒ [0, 0, 0, 0, 0, 0]
// i = 63 ⇒ [1, 1, 1, 1, 1, 1]
// bs[0], bs[1], bs[2], bs[3], bs[4], bs[5]
// A, B, C, D, E, F
b1 = bs[0]||bs[1];
b2 = (bs[0]&&bs[4])||(bs[0]&&bs[5])||(bs[4]&&bs[5]);
b3 = !(bs[0]&&bs[3]);
b4 = (bs[1]&&bs[2])||(!bs[1]&&!bs[2]);
b5 = (bs[2]&&!bs[3])||(!bs[2]&&bs[3]);
b6 = bs[3]||!bs[4];
// 非D ⇒ 非E 等价于 D||非E
if (b1&&b2&&b3&&b4&&b5&&b6)
// 每个条件都满足
{
std::cout << "A: " << judge[bs[0]] << std::endl;
std::cout << "B: " << judge[bs[1]] << std::endl;
std::cout << "C: " << judge[bs[2]] << std::endl;
std::cout << "D: " << judge[bs[3]] << std::endl;
std::cout << "E: " << judge[bs[4]] << std::endl;
std::cout << "F: " << judge[bs[5]] << std::endl;
break;
}
}
return 0;
}
每个人只说对了一半
关于五位选手的排名,各做如下的陈述:
- A:B第二,我第三
- B:我第二,E第四
- C:我第一,D第二
- D:C最后,我第三
- E:我第四,A第一
每个人对了一半,错了一半。请编程给出正确的名此。
根据排列组合的知识,我们知五个人排名,所有可能性的个数为: A55=120 ,我并没有构造出合适的for循环,控制解空间的大小,无奈暂时只得用五重的for循环+if判断:
int b1, b2, b3, b4, b5;
for (int A = 1; A < 6; ++A)
for (int B = 1; B < 6; ++B)
for (int C = 1; C < 6; ++C)
for (int D = 1; D < 6; ++D)
for(int E = 1; E < 6; ++E)
{
// 本质上表达的是5^5=3125的概念
if (A*B*C*D*E=120)
{
b1 = (B==2) + (A==3);
b2 = (B==2) + (E==4);
b3 = (C==1) + (D==2);
b4 = (C==5) + (D==3);
b5 = (E==4) + (A==1);
if (b1*b2*b3*b4*b5==1)
{
cout << A << B << C << D << E << endl;
break;
}
}
}
后来在我晚上跑步的时候,丑陋的五重循环始终折磨着我,终于让我想出了如何用组合
A55
的形式,完美的解决这一问题,下述的代码用到了STL<algorithm>的next_permutation
,具体请参阅该函数的相关说明:
#include <iostream>
#include <algorithm> // next_permutation
#include <iterator>
int main(int, char**)
{
int ranking[] = {1, 2, 3, 4, 5};
int b1, b2, b3, b4, b5;
bool flag = true;
while(flag)
{
b1 = (ranking[1] == 2) + (ranking[0] == 3);
b2 = (ranking[1] == 2) + (ranking[3] == 4);
b3 = (ranking[2] == 1) + (ranking[3] == 2);
b4 = (ranking[2] == 5) + (ranking[3] == 3);
b5 = (ranking[4] == 4) + (ranking[0] == 1);
if (b1*b2*b3*b4*b5 == 1)
{
std::copy(ranking, ranking+5, std::ostream_iterator(std::cout, " "));
std::cout << std::endl;
break;
}
flag = std::next_permutation(ranking, ranking+5);
}
return 0;
}