『一道很難的智力題』解法

題目鏈接:一道很難的智力題 - ⑥的完美算數教室 - 知乎專欄

首先很抱歉把這道智力題寫成了專欄而不是問題(本意只是想分享有趣的智力題),導致大家回答起來比較吃力,沒想到有這麼多人感興趣 :)

其次,除了我這個解法外,還有來自Vichare Wang的解法。我感覺他的答案是對的,而且操作起來比我的做法簡單許多。另外,還有來自wheeler的解法,我還沒來得及仔細看。

最後,本解法來自StackExchange上Gil Goldshlager的解法。我試圖把原解法寫得盡量降低讀者的思維深度需要(因此答案會顯得非常冗長,但我希望易懂)。但無論如何,credit都歸原作者。

=======================

前言

在開始之前,請大家先注意一個很重要的結論:所有人排成了一個圓,那麼在任何時候,當天{發出1的那部分人}{收到1的那部分人}不可能完全相同。否則,這些人自己就形成了一個閉環。唯一的例外是:發出者已經是全集(或者空集)。換句話說,對於某個非空真子集來說,他們一定有辦法對外『滲透』,同時也一定可以『吸收』來自外部的新信息,獄長無法阻止。

這個結論在本解法中經常用到。下文中我會用來提醒大家回想這個結論。

=======================

序章:打造一個工具

我們先來定義一個子過程tryFill(S, k),其中S是一個人員集合,k是某個正整數。並且假定所有人都明白自己是否在S里,且所有人都知道k的值是多少。

tryFill的目標是試圖讓1『感染』所有人,它包含兩步:

第一步耗時k天。在這k天里,所有在S里的人,以及在這一步內收到過1的人,都持續發1直到這一步結束。其他人發0。

這有什麼用呢?顯然是從S這個『傳染源』開始,試圖讓1感染盡量多的人。多少人呢?如果每次收到1的人都是『新』的(還沒被感染的),那麼『患者』每輪都加倍,最多感染left| S 
ight| 	imes 2^k個人。但如果收到1的人有很多『舊』的怎麼辦?根據,每次收到1的人至少會有1個是『新』的。這也就意味著第一步至少感染了|S|+k個人,除非總人數沒有那麼多——那就已經感染了所有人

所以我們可以這麼說:第一步結束後,要麼所有人都被感染了(成功),要麼只有一部分人被感染(失敗),這部分人不超過left| S 
ight| 	imes 2^k個。

現在問題就來了:第一步結束後,如果某人仍未被感染,那他清楚地知道本次tryFill『失敗』了。但是反過來,如果他被感染,他還沒法肯定這次實驗就是『成功』的——因為他不知道是否有其他人沒被感染。換句話說,此時還未達成一個關於『這次是成功了還是失敗了』的全局『共識』,這怎麼辦?再次利用,我們可以反過來去嘗試『清除』這些人的感染,一天至少清除一個(除非前面已經『成功』)。既然上面說了第一步最多感染left| S 
ight| 	imes 2^k個,那麼:

第二步耗時left| S 
ight| 	imes 2^k天。在這些日子裡,所有尚未被感染(即第一步最後一天收到的還是0),以及在這一步內收到過0的人,都持續發0直到這一步結束。其它的人發1。

經過這兩步之後,要麼所有人都是1(成功),要麼所有人都是0(失敗)。這意味著達成了共識——每個人都知道本次tryFill是成功了還是失敗了!

要注意的是,為了保證每個人都知道第二步一共要執行多少天,要麼每個人都必須知道left| S 
ight| 的值,要麼某個『總人數的上界U』已經成為共識——這時,這個『清除感染』的過程只要執行U天就足夠了

以上,子過程tryFill定義完畢。

=========================

第一章:找出人數上界

tryFill出山的第一戰,就是用來找出這個『總人數的上界U』:

我們以給大家群發郵件的『你』作為傳染源,從k=1開始調用tryFill({}, k)。若失敗,則把k增加1再重試,如此反覆。由於每輪感染的人數下限k+1不斷增加,所以一定有成功的那一天。而一旦在k=k_0成功,我們就知道總人數不超過U=2^{k_0}個人,人數上界找到並成為共識。

然而為什麼要找上界呢?在tryFill的最後曾提到過,使用tryFill的前提需要知道|S|U二者之一。現在我們找到了U,從此就解鎖了一項技能——可以對任何大小未知的集合S進行tryFill操作。這讓我們發現了tryFill的另一種用途:

對於任意(可以是空集的)集合S,調用tryFill(S,U)。只要S不是空集,由於第一步至少感染了U個人,tryFill必然成功。這樣大家就可以根據tryFill(S,U)成功與否,來判斷S是不是空集。換句話說,除了幫我們找到上界之外,我們同時有了一個新工具:isEmpty(S),並且它仍然保持著『全局共識』這樣一個優良傳統。

=========================

第二章:拆!

接下來,我先給出解決問題的方向,以及具體執行方案。在下一章里再給出分析,來解釋這個執行方案為什麼能夠解決問題。

方向:我們試圖把所有人拆分成若干個不相交的集合{S_1, S_2, ..., S_n},並在這個過程中找到關於這些集合之間的一系列關係。更具體地說,如果把每個集合的大小x_i=|S_i|看做未知數,我們試圖建立若干個關於這些x_i之間的方程,並且證明這個方程組有唯一解的。

執行方案:

起始時,我們把所有人劃分成n=2個集合:S_1={}S_2={其他人}

現在假設場上存在n個集合,那麼我們定義如下操作為『一輪』:

1. (外循環)按照某種事先約定好的順序來遍歷mathbb{S}={S_1, S_2, ..., S_n}的所有非空真子集,也就是說每個子集都包含著若干個S。在這個循環里,記『當前子集』包含的所有S的下標為下標集合A

1.1. 花一天時間,讓A所代表的那些集合里的每個人都發1,其他人發0。這樣會有一些人收到了1,記這些人構成集合F

1.2. (內循環)對於每個A,再按j=1,2,...,n的順序遍歷每個集合S_j

1.2.2. 執行isEmpty(S_jcap F),也就是判斷S_j里有沒有人在1.1步中收到了1。

1.2.3. 執行isEmpty(S_jsetminus F),也就是判斷S_j里有沒有人1.1步中收到了0。

1.2.4. 若以上兩個集合都不為空,說明S_j里既有人收到1也有人收到0。那麼把原來的S_j分裂為兩個新的集合。不妨把『收到1』的那部分人繼續沿用S_j,而『收到0』的那撥人則新開設一個集合S_{n+1},同時把集合總數n加一。反之,若以上兩個集合里有一個為空,那麼S_j就保持原樣。

1.2.5. 若1.2.4步中成功分裂了一個集合,那麼這『一輪』就立即結束,並且重新開始一個新的『一輪』。反之,若這輪中的所有循環都未能成功分裂某個集合,就不再重複進行新的『一輪』了。

不難想像,每一輪都會分裂一個集合,但集合總數不可能超過全場人數,所以總有停止的一天。

現在我要證明的是,根據最後一輪(即沒有任何集合分裂)里發生的一切,場上的每個人都有能力計算出所有現存集合的大小,從而得到總人數!

=========================

最終章:為什麼會這樣呢

讓我們來看看『最後一輪』里都發生了什麼:

每一個外循環對應著一個AA所代表的那些集合里的每個人都發了1,這些1被F收到了。由於本輪沒有集合被分裂,這意味著F可以視為若干個S的並集——因為任何一個集合S,要麼S里的所有人都收到了1,要麼都沒收到1,(否則就該分裂)。不妨記這些『收到1』的集合的編號構成了編號集合Z。我們可以簡單地概括成『A發出信號,被Z收了』。

注意,由於遍歷mathbb{S}的所有非空真子集的順序是事先約好的,所以每個人都知道A代表哪些集合。同時,由於所有人都知道isEmpty的結果,也就是說大家都知道Z里有哪些集合(的下標)。

現在我們知道AZ的內容都是共識。另外,我們還知道每天發出1的人數總是等於收到1的人數。這意味著我們得到一個這樣的方程:

sum_{iin A}{x_i}=sum_{jin Z}{x_j}

用人話說,就是『A代表的集合的人數總和與Z代表的集合的人數總和相同』。另外,由於A來自於mathbb{S}的所有非空真子集,所以A不會是全集,根據,A
eq Z總是成立的。

類似這樣的方程有多少個呢?它等於mathbb{S}的所有非空真子集的個數,也就是2^n-2個。有了這個n元線性方程組,我們就能用高斯消元法來解出每一個x_i。但在這之前,還需要證明這個方程組有唯一解。為此,我們需要用數學歸納法證明一個更泛的結論:

當係數p_j已知時,由類似上述方程的方程sum_{iin A}{x_i}=sum_{jin Z}{p_jx_j}構成的方程組總是有唯一解的。

證明如下:

n=2時,方程組退化為x_1=p_2x_2。由於已知x_1=|{}|=1,也知道p_2,顯然可以解出唯一解x_2

假設n=k-1時結論成立,此時方程組的左邊包含了{x_1,x_2,...,x_{k-1}}的所有組合。那麼當n=k時,考慮某個不含x_k的未知數集合X,我們能在方程組裡找到這樣兩個方程:

X=X_1

X+x_k=X_2

如果我們能用以上兩個方程構造出一個類似X=X的方程,使得X不含x_k,那我們就能用所有n=k的方程來構建出所有n=k-1所需的方程,問題就搞定了。能么?顯然可以。

如果X_1不含x_k,直接用X=X_1即可。

如果X_2p_kx_kp_k=1,用X=X_2-x_k即可。

唯一剩下的情況是X_1x_k同時X_2無法完全抵消左邊的x_k。此時,顯然可以用代入消元法消去x_k形成一個左邊為X的方程。

至此,整個問題解答完畢。


推薦閱讀:

我有一個1*n的格子帶,上面有n個單位格子,需要把其中m個格子染上色。我現在有三種演算法,哪種符合要求?
這套神奇的演算法,比網易雲音樂更懂你
遊戲中渲染層實體如何平滑的做插值?
Google旗下機器人Atlas步行演算法再升級,成功挑戰困難地形

TAG:趣味数学 | 智力游戏 | 算法 |