一個與OI本身並木有什么聯系的問題,只是感覺很有趣而且驗證過程用到了計算機(主要是懶得手算),便花一點點時間來寫一下。
問題
一天,鬼谷子隨意從2-99中選取了兩個數。他把這兩個數的和告訴了龐涓,把這兩個數的乘積告訴了孫臏。但孫臏和龐涓彼此不知道對方得到的數。第二天,龐涓很有自信的對孫臏說:“雖然我不知道這兩個數是什么,但我知道你一定也不知道。”隨后,孫臏說:“那我知道了。”接着龐涓說:“那我也知道了”。
請問這兩個數是什么?
解法
看似很無厘頭的對話,但這個問題確實有解。世界真奇妙。
接下來讓咱們一句句分析:
-
龐涓:“我不知道這兩個數是什么”
這句話……跳過。其實它也不是沒有蘊含信息(這句話表明龐涓得到的數不是2、3或197、198),但信息實在太少了。不管它就行了。
-
龐涓:“但我知道你不知道”
翻譯一下就是,龐涓肯定自己的數不存在一種分解使得它們的乘積告訴孫臏之后他能立馬猜出這兩個數。
首先第一步,我們可以反向思考,孫臏得到哪些數之后,能立馬猜出來?
一種可能是它是兩個質數的乘積,原因顯而易見。另一種是這個數是53與另外一個數的乘積,因為這樣的乘積也都只有一種分法(53\(\times ?\)),畢竟只要再分給53一個因子第一個乘數就超范圍了。
整理一下,龐涓拿到的數不會大於54(因為保不准鬼谷子想到的數就有53),也不會能等於兩個質數之和。那……都有哪些數?
上代碼。其中check函數為判斷是否為質數的函數。
for(int i=4;i<=54;i++){
bool ok=true;
for(int j=2;j<=i/2;j++)
if(check(j)&&check(i-j)){ok=false;break;}
if(ok)printf("%d ",i);
}
輸出:11 17 23 27 29 35 37 41 47 51 53
-
孫臏:“那我知道了”
沒錯我們的懂王聽完龐涓的話之后懂了。這句話說明什么?首先要明確一點,既然到這一步為止我們已經推斷出龐涓手上可能的數有哪些,那么機智過人的孫臏肯定也早就得出了這個數列。而他懂了,說明他手上的數分解后所有可能的兩數之和有且只有一個在上述集合之中。
很繞,非常繞。但確實求得出來有哪些可能的值。代碼中A數組記錄了上面得到的11個數,下標從1開始。
for(int i=1;i<=11;i++){
for(int j=2;j<=A[i]/2;j++){
num[j*(A[i]-j)]++;
}
}
for(int i=1;i<1000;i++)if(num[i]==1)printf("%d ",i);
輸出(后面的輸出不重要)18 24 28 50 52 54 76 92 ......
-
龐統:“那我也知道了”
沒錯我們的另一個懂王聽完孫臏的話之后也懂了(tmd就你們最懂)。這說明什么?套用剛才的分析方法,我們可以知道,龐統應該也得到上面我們輸出的那一長串數了。而龐統現在懂了,說明什么?說明他手上的數只有一種方法可以拆分為兩個數使得這兩個數的乘積在上面的那個集合之中。接下來的事情不就好辦了嗎,枚舉就行了啊……
代碼是在上一步的基礎上完成的。
for(int i=1;i<=11;i++){
for(int j=2;j<=A[i]/2;j++){
num[j*(A[i]-j)]++;//代表此乘積的方案數加一
fr[j*(A[i]-j)]=i;//記錄這個乘積是由哪個和轉移過來的
}
}
for(int i=1;i<1000;i++){
if(num[i]==1){//如果當前i在上一步得到的那個奇怪的集合中
use[fr[i]]++;//記錄這個和可能的種數
one[fr[i]]=i;//順便記錄一下從哪里來的
}
}
for(int i=1;i<=11;i++){
if(use[i]==1){//如果這個和只有一種分解方法說明他是正解
printf("%d %d",A[i],one[i]);
//注意此時輸出的是鬼谷子想的兩個數的和與積
}
}
輸出17 52
沒錯,只有一種可能。
-
最后一步
問題變成了已知兩個數的和與兩個數的積,求這兩個數。這就有點侮辱人了對吧。顯然可以求出這兩個數分別是13和4。然后就完了。
當時是在一個奇怪的CSP-J初賽中做到的這道題,當時頓時懷疑人生(好家伙入門組都這么難那還玩個鬼),下來之后看網上各種題解,發現其中使用各種各樣的分析技巧和數論知識本蒟蒻都不會,便想到能否用其它方法來搞定這道題。於是,學過\(10^{-43}\)秒信競的我決定秉承着“暴力出奇跡打表拿省一”的精神暴力枚舉(其實數據規模也不大),沒想到真的枚舉出來了。特寫此文紀念。
原創不易哦(你應該知道我想說啥吧)。