国王招来100个囚犯,对他们说:你们犯的是死罪,本应该将你们统统杀掉,但我慈悲为怀,给你们一次求生的机会。15分钟以后,你们将被关进一个有100间隔离牢房的监狱里,每人一间牢房,都与外界隔绝,什么也听不见、看不到,连时间都没法计算,更别说获得外界的任何信息。(送饭除外,但也是不规律的送)
这所监狱有一个院子,每天会随机(注意是完全随机)打开一间牢房的门,让那个囚犯到院子里来放风。院子里有一盏路灯,放风的囚犯可以控制它的开关,将它打开或是关闭。除囚犯之外,其他人都不会去碰开关。这盏灯会永远有充足的能源供应,如果灯泡坏了或是电路出了故障会马上修好,当然修理人员不会改变灯的状态(开或关)。
除了开关这盏灯,放风的囚犯放风时留下的任何其它痕迹都会在夜晚被清除干净(包括在灯上作的任何记号)。
牢房是完全封闭的,院子里的灯光在牢房里看不到。只有放风出到院子里的人才能看到。
好了现在我向你们提出一个要求,只要你们做到了,就可以全部获得释放: 若干天以后,你们中只要有任何一个人能够向我证明所有的人都曾到院子里去过,你们就全体释放。当然要有证据!因为我只会给你们一次机会,如果向我证明的那个人无法自圆其说,你们就全部砍头。所以,要珍惜这次机会。如果你们永远做不到我的要求,你们就全部关到死。
现在给你们15分钟商量你们的方案。15分钟以后,你们将被关进我刚才说的那个监狱,永远无法再交流。
初始思路:
第一个出去的人的任务:开灯,且只负责开灯。 其他所有人的任务:关灯,且只负责关灯,且只在自己从未关过灯的情况下才可以关灯
这样第一个人经过无数次放风后,一共看到了99次关着的灯,他们就自由了
网友思路:
1、囚犯不知道时间,也就是说他们不知道自己会是第几个人。根据条件,他们更不知道是第几天。
2、囚犯随机的放风。这里可能出现同一囚犯放风多次的情况。
3、那个路灯是他们唯一交流的工具。
4、现在最大的问题是不知道灯的初始状态。
我的办法:
1、指定计数囚犯。100囚犯中指定一个唯一的计数囚犯。
2、规则:
(1)所有囚犯除计数囚犯外,当他是第1次放风的时候,如果灯是关着的则设置灯为开,以后再放风,不干涉灯的状态。但第1次放风时,如果灯是开着的,则自己认为自己没放过风。
(2)计数囚犯统计灯开的次数,统计之后将灯关闭。计数到99时,结束。
细节解释:当计数囚犯第1次出门,如果灯是开着的。会有3种情况:1、灯默认是开着的,他是第1个放风的。2、灯是默认开着的,其他囚犯没有去碰开关。3、灯初始是关着的,一放风囚犯将它改为开。
现在问题出现了,由于不知道灯的初始状态,所以在计数囚犯计第1个数时是有误差的,误差为1人。但如果,计数囚犯放风时看到灯是关着的,那就万事大吉了。
这个思路蛮不错的,但是我还有个疑问:
计数员关灯99次的时候,就说明大家都出来过了,如果灯最初是开着的,那么第一个将灯关闭的人肯定是计数员,计数员认为已经有一个人出来过了(其实还没人出来过),当出来到第98个的时候,计数员就已经数到99,认为大家都出来过了,然后大家都被拉出去砍头了。如果计数员考虑到这种情况,打算多数一个到100,当初始灯是关着的时候,他们就永远都出不去了,因为大家都只开一次,永远都到不了100.
这个思路蛮不错的,但是我还有个疑问:
计数员关灯99次的时候,就说明大家都出来过了,如果灯最初是开着的,那么第一个将灯关闭的人肯定是计数员,计数员认为已经有一个人出来过了(其实还没人出来过),当出来到第98个的时候,计数员就已经数到99,认为大家都出来过了,然后大家都被拉出去砍头了。如果计数员考虑到这种情况,打算多数一个到100,当初始灯是关着的时候,他们就永远都出不去了,因为大家都只开一次,永远都到不了100.
java实现的DEMO:
- import java.util.Random;
- public class Prisoner2 {
- /**
- * 犯人数目(大于1)
- */
- private static final int PRISONER_COUNT = 100;
- /**
- * @param args
- */
- public static void main(String[] args) {
- int minYear = Integer.MAX_VALUE;
- int maxYear = Integer.MIN_VALUE;
- for (int i = 0; i < 100; i++) {
- int year = prisonBreak();
- if (minYear > year) {
- minYear = year;
- }
- if (maxYear < year) {
- maxYear = year;
- }
- System.out.print(prisonBreak() + ",");
- }
- System.out.println();
- System.out.println("MinYear:" + minYear);
- System.out.println("MaxYear:" + maxYear);
- }
- private static int prisonBreak() {
- Random r = new Random();
- boolean lightOn = r.nextBoolean(); // 灯的初始状态
- int[] prisoner = new int[PRISONER_COUNT]; // 犯人,其值为开灯次数,最多两次
- final int COUNTER_ID = r.nextInt(PRISONER_COUNT); // 随机指定计数员,只负责关灯
- int counter = 0; // 计数员记下的关灯次数
- int days = 0; // 总共所花的天数
- int freeCount = (PRISONER_COUNT - 1) * 2; // 关灯次数达到此数时就自由了
- while (counter < freeCount) {
- days++;
- int n = r.nextInt(PRISONER_COUNT); // 随机出来放风的犯人
- if (n == COUNTER_ID) {// 计数员
- if (lightOn) { // 灯是开的就关灯
- lightOn = false;
- counter++;
- prisoner[COUNTER_ID]++; // 计数员记的是关灯数
- }
- } else { // 其他犯人
- if (!lightOn && prisoner[n] < 2) { // 灯是关的,且开灯次数不超过2
- lightOn = true;
- prisoner[n]++;
- }
- }
- }
- // 检查结果
- int sum = 0;
- for (int i = 0; i < PRISONER_COUNT; i++) {
- if (prisoner[i] > 0) { // 开过灯,则证明其放过风
- sum++;
- }
- }
- assert sum == PRISONER_COUNT : "something wrong.";
- return days / 365; // 大约
- }
- }
最佳逻辑方案,可以不考虑灯的初始状态:
很明确的条件:
1、每人一间牢房,都与外界隔绝,什么也听不见、看不到,连时间都没法计算
说明:没有人知道自己是第几天出来,所以不要再讨论说哪个囚犯是第一天出来就是记数员
2、每天会随机(注意是完全随机)打开一间牢房的门,让那个囚犯到院子里来放风。
说明:随机抽取,囚犯只能知道自己出来跟回去,以及看到灯的状态
我们在这里,只是为解题而解题,不需要考虑题目本身的问题,不用管花了多少天多少年,国王是否会死掉的情况。
如果条件是每10分钟随机放一个囚犯出来,那就快很多了,所以没必要去理会命题本身的合理性,只要考虑是否有
100%的不失误的方案,不带一丝侥幸。
解题方案:
1、指定99个一般囚犯,他们的有两个行为:一是把状态是开的灯关掉,否则不动;二是统计自己关掉灯的次数,如果有两次后,就再也不去开或者关这个灯。
2、指定1个记数员囚犯,他只有两个行为:一是把状态是关的灯打开,否则不动;二是负责统计从自己第一次(注意:不是第一天)出来开始算,(如果自己第一次出来灯是亮的,那么证明自己是第一个出来,同时也证明灯开始状态是亮的;如果自己第一次出来灯是暗的,那无法确认灯开始的状态以及自己是第几个出来)灯的被关次数达到198次,那么就可以确认所有的人都曾到院子里去过。