从之前的分析中可以看出,该问题的关键在于村民如何看待红眼人的存活,因为只有生死状态才是他们判断红眼睛数量信息的唯一依据。
假设 N N N个村民全部都是红眼睛,记为 { x 1 , … , x n } \{x_1,\dots,x_n\} {x1,…,xn},很显然这里每个人都能看到 N − 1 N-1 N−1双红眼。他们知道自己看到了 N − 1 N-1 N−1双红眼,这并不代表他们知道别人也看到了 N − 1 N-1 N−1双红眼,因为自己和别人自己的状态对别人是未知的。
我们不妨把 { x 1 , … , x n } \{x_1,\dots,x_n\} {x1,…,xn}视为一个有 N N N个节点的无向完全图G,其中每个边表示 x i x_i xi知道 x j x_j xj的眼瞳状态。显然 x 1 x_1 x1知道从 x 2 x_2 x2到 x n x_n xn的眼瞳颜色,那么对于 x 2 x_2 x2知道多少双眼瞳,这个信息 x 1 x_1 x1知道多少?只要在G中剥掉节点 x 1 x_1 x1和 x 2 x_2 x2,由此得到的信息就是 x 1 x_1 x1所掌握的、关于 x 2 x_2 x2观察眼瞳的信息。
x 2 x_2 x2掌握 x 3 x_3 x3观察到多少眼瞳,这一信息也可以通过从G剥夺节点 x 2 x_2 x2和 x 3 x_3 x3来发现。而这一信息对于 x 1 x_1 x1掌握多少,只需要再剥夺节点 x 1 x_1 x1就行。
那么,不断重复这一过程,我们很快发现,最后肯定会留下一个节点 x j x_j xj,它被 x i x_i xi推断为没有观察到任何一双红眼瞳。
这不是说 x j x_j xj没看到其他红眼瞳: x j x_j xj当然看得到,但这一信息并不被其他人共享。
不妨假设 x N x_N xN被 x 1 x_1 x1推断没有观察到任何红眼瞳。这不是说 x N x_N xN没有观察到从 x 1 x_1 x1到 x N − 1 x_{N-1} xN−1的红眼瞳,而是说从 x 1 x_1 x1到 x N x_N xN的红眼瞳信息并不被 { x 1 , … , x n } \{x_1,\dots,x_n\} {x1,…,xn}所有人共享。
实际共享的眼瞳信息为 ∩ i = 1 N { x 1 , … , x n } / { x i } = ∅ \cap_{i=1}^{N}\{x_1,\dots,x_n\} / \{x_i\}=\empty ∩i=1N{x1,…,xn}/{xi}=∅
在旅客宣布有红眼睛存在之前,这一推断被允许成立。
而在旅客宣布之后,这一推断被推翻,这意味着 x 1 x_1 x1知道了 x N x_N xN观察到了从 x 2 x_2 x2到 x N − 1 x_{N-1} xN−1的任意一双眼瞳。
也就是说实际共享的眼瞳信息为 ∅ ∪ { x j } \empty\cup\{x_j\} ∅∪{xj},其中每个人对 j j j可取不同值,只要 ∥ ∅ ∪ { x j } ∥ \|\empty\cup\{x_j\}\| ∥∅∪{xj}∥相等,即元素数量相等。
于是我们就可以顺理成章地推出:这些村民会选择自杀,当且仅当共享信息的眼瞳数量 ∥ ∅ ∪ { x j } ∥ \|\empty\cup\{x_j\}\| ∥∅∪{xj}∥大于自己观察的数量。而这个共享信息在每晚过去之后都会自增 1 1 1——只有他们选择自杀时、这一自增才会停止。
通过软件模拟,可以实现上述流程。以下为Python实现。
import numpy as np
import argparse
def get_watchlist(N,red_eyes):
watchlist=list()
for idx in range(N):
ct=0
for r in red_eyes:
if r!=idx:
ct+=1
watchlist.append(ct)
return watchlist
def is_anyone_die(livelist):
return any(livelist)
def day_passed(livelist,watchlist,selflist):
for i in range(len(livelist)):
if livelist[i]==0: # alive
if watchlist[i]<1:
if selflist[i]!=0 and (not is_anyone_die(livelist)):
selflist[i]=1
elif is_anyone_die(livelist):
selflist[i]=0
else:
watchlist[i]-=1
def day_counted(livelist,selflist):
one_died=False
for i in range(len(livelist)):
if livelist[i]==0 and selflist[i]==1:
livelist[i]=1
one_died=True
return one_died
def parse_args():
parser=argparse.ArgumentParser("Simulator for the red-eye problem")
parser.add_argument("N",type=int,help="The number of total villages.")
parser.add_argument("M",type=int,help="Number of red eye persons")
args=parser.parse_args()
return args
if __name__=='__main__':
args=parse_args()
N,M=args.N,args.M
if not (M>0 and N>=M):
print(f"Arguments M={M} and N={N} are not valid! Make sure 0<M<=N!")
else:
count_days=M
red_eyes=np.random.choice(a=N,size=(M,),replace=False).tolist()
livelist=[0]*N
watchlist=get_watchlist(N,red_eyes)
selflist=[-1]*N
for i in range(count_days):
day_passed(livelist,watchlist,selflist)
day_counted(livelist,selflist)
died=sum(livelist)
print(f"Day {i+1}: {died} dead.")