《程式設計之美》螞蟻爬桿問題的擴充套件

2021-07-04 21:34:29 字數 3733 閱讀 7805

《程式設計之美》4.7節描述了螞蟻爬桿問題,把所有具體數字都表示成字母後變為形如如下形式的問題:

有一根長為l

的平行於x軸的細木桿,其左端點的x座標為0(故右端點的x座標為l

)。剛開始時,上面有n

只螞蟻,第i(1

≤i≤n

)只螞蟻的橫座標為x

(假設x

已經按照遞增順序排列),方向為d

(0表示向左,1表示向右),每個螞蟻都以速度v

向前走,當任意兩隻螞蟻碰頭時,它們會同時調頭朝相反方向走,速度不變。編寫程式求所有螞蟻都離開木桿需要多長時間。

該問題是經典問題了,有o(n

)的解法。昨天和趙牛同學討論了該問題的一些擴充套件,趙牛均給出了精妙解答,現列出如下:

第i只螞蟻什麼時候走出木桿?

所有螞蟻從一開始到全部離開木桿共碰撞了多少次?

第k次碰撞發生在哪個時刻?哪個位置?哪兩個螞蟻之間?

哪只螞蟻的碰撞次數最多?

如果不是一根木桿而是乙個鐵圈,經過一段時間後所有螞蟻都會回到的狀態嗎?這個時間的上界是多少?

擴充套件1的解答

現在來解決擴充套件1。這個解答甚是精妙,通俗點來說,我們假設每只螞蟻都揹著一袋糧食,任意兩隻螞蟻碰頭時交換各自的糧食然後調頭。這種情況下,每次 有乙隻螞蟻離開木桿都意味著一袋糧食離開木桿(雖然可能已經不是它剛開始時背的那一袋了)。於是,我們可以求出每袋糧食離開木桿的時間(因為糧食是不會調 頭的)。又由於每袋糧食離開木桿的時間都對應某只螞蟻離開木桿的時間,這是一種一一對映關係。現在我們要找到對應於第i

只螞蟻的那個對映。在此之前需要證明乙個命題:

若一開始時有m

只螞蟻向左走,n−m

只螞蟻向右走,則最終會有m

只螞蟻從木桿左邊落下,n−m

只螞蟻從木桿右邊落下。

這個命題很容易證明:每次碰撞均不改變向左和向右的螞蟻數量。於是,由於每次碰撞螞蟻都會調頭而不是穿過,最後必定是前m

只螞蟻從左邊落下,後n−m

只螞蟻從木桿右邊落下。由於我們知道每袋糧食是從哪邊落下的,故左邊落下的m

袋糧食的離開木桿的時間就對應於前m

只螞蟻離開木桿的時間,右邊的類似。因此,我們只需判斷i

和m的關係,便知道第i

只螞蟻是從左邊還是右邊落下。不妨假設是從左邊落下,因此該螞蟻落下的時間就等於從左邊落下的第i

袋糧食的落下時間。時間複雜度o(n

),一遍掃瞄搞定。

擴充套件2的解答

對於擴充套件2,我們只需求得每個螞蟻的碰撞次數,然後累加即可。在這裡我們換一種思路,把碰撞調頭看成不調頭而繼續向前(穿過),則容易看出原問題 (碰撞次數)就變為求穿過的次數(因為速度大小不變,原來的每次碰撞都對應於現在的一次穿過)。則對於每只向左的螞蟻,它只會「穿過」那些在它左邊的向右 走的螞蟻。因此,每只螞蟻「穿過」的螞蟻次數等於剛開始時在它前進方向上與它前進方向相反的螞蟻個數。時間複雜度也是o(n

)。改為用糧食的觀點來理解也是可以的。

擴充套件3的解答

第3個擴充套件問題有點複雜。首先我們假設v

為0.5個單位長度每秒,每個螞蟻剛開始時都處於整點處,這樣每次碰撞都發生在整秒處。這個假設有個好處,就是我們可以二分第k

次 碰撞的時刻。如果碰撞時刻是浮點數,這個二分有可能永遠不會終止。我們還是看成每個螞蟻馱著一袋糧食,那麼每袋糧食易主(即從乙個螞蟻身上交換到另乙個螞 蟻身上)時,就發生了一次碰撞。由於糧食的方向是固定不變的,我們可以很容易求出每一袋糧食在它的「前進」方向上的所有易主時刻(它易主的次數等於擴充套件2 中的「穿過」次數)。這樣,我們的問題就等價於:

找到最小的時間t

,使得易主時刻小於或等於t

的易主次數大於或等於k

。由於現在所有碰撞(易主)的時刻都是整點,故我們可以二分t

,然後用線性複雜度找出易主時刻小於或等於t

的易主次數。整個複雜度為o(n

∗log

(|ma

xt−m

int|

),其中maxt和mint分別表示第一次和最後一次碰撞的時刻,均可在o(n

)時間內求出。

在上一段中,要想使用線性時間複雜度求出易主時刻小於或等於t

的易主次數還需要一點技巧。可以這樣:用乙個陣列p

表示第i

個向右走的螞蟻的初始位置,當掃瞄到第j

個向左走的螞蟻時,假設得到的中值點為i

(即p到第p

個位置上對應的糧食和該袋糧食的易主時刻均大於t

)。由於該袋糧食向左易主的時刻是遞增的,而下乙個向左走的螞蟻的初始位置又大於當前(第j

個向左走的)螞蟻,故對於下乙個螞蟻ant來說,p

到第p個位置上對應的糧食和ant所馱糧食的易主時刻也一定大於t

。即中值點的位置是單調的。因此可以在均攤o(n

)的時間內算出所求個數。

求出時刻的同時我們也求出了位置,故第二小問也解決。接下來要求哪兩個螞蟻發生了這次碰撞(如果同時存在多個碰撞求出任意乙個即可)。其實,我們只需要求出每袋糧食在t

時刻的位置即可。因為每袋糧食必然對應於乙個馱著它的螞蟻,故我們只需對這些糧食的位置排序,找出位置相同的糧食以及其下標(即從左到右第幾個),也就找出了那對螞蟻了。

擴充套件4的解答

對於第4個擴充套件,只要求出每只螞蟻的碰撞次數即可。這也解決了擴充套件2的解答中原始思路。首先由擴充套件1的解答我們可以知道每只螞蟻最終是往左還是右掉下去,然後假設第i

只螞蟻最終往左掉下,而開始時刻其左邊有r

只向右走的螞蟻,則它至少要朝左邊碰撞r

次才能把左邊的螞蟻全撞成向左的狀態。倘若它一開始就是向左的,則共要碰撞2

r次,否則為2r+

1次。這樣利用乙個陣列和幾個計數器仍能在o(n

)時間內求出每個螞蟻的碰撞次數,取最大那個即可。

擴充套件5的解答

這個問題看起來挺複雜,其實很簡單。假設環長為l

,則乙個螞蟻走完一圈需要t=l

/v的時間。首先,還是像上面的討論那樣假設每個螞蟻都馱著一袋糧食。那麼,經過t

時間後所有糧食都回到了原來的位置。由於每袋糧食都對應乙個螞蟻,而螞蟻每次碰撞都會調頭,因此螞蟻的相對位置是不變的,這就說明經過t

時間後螞蟻迴圈移動了。假設移動了s

個位置,即每個螞蟻都到達它往右第s

個螞蟻的初始位置,那麼,類似地,再經過t

時間,當前狀態仍會迴圈移動s

個位置。容易看出這是乙個最小公倍數問題:迴圈移動多少個s

次之後每個螞蟻回到自己位置?答案為lcm

(n,s

)/s,於是最多經過tma

x=lc

m(n,

s)/s

∗t≤n

∗t時間,每個螞蟻都至少回到原地一次。

除了以上幾個擴充套件,還有一些個人認為比較**的擴充套件,有的沒空仔細想,有的暫時沒想到解法,也列出如下,歡迎拍磚:

如果每只螞蟻的速度不一樣(這就有可能由於追趕而產生碰撞,此時根據動量守恆定律:(,速度互換),上述擴充套件問題的答案是什麼呢?

如果螞蟻在乙個平面上運動,同樣也是碰頭後原路返回(注意這不等同於兩隻螞蟻交換繼續前進),問是否所有螞蟻都能最終離開平面?

在上述情況下,如果最終能離開平面,離開平面需要多長時間?

在上述情況下,回答關於一維的前文討論的每個問題。

另外,趙牛同學又提出了一些更bt的擴充套件,如下:

假設每個螞蟻都有重量,兩隻螞蟻碰撞時輕的那個有一定機率從旁邊被撞下去:(,那又該怎樣?

假設不是被撞下去而是有一定機率被撞暈而停滯幾秒,那又該怎樣?

blablabla...

程式設計之美 4 7螞蟻爬桿

問題 有一根27厘公尺的細木桿,在第3厘公尺 7厘公尺 11厘公尺 17厘公尺 23厘公尺這五個位置上各有乙隻螞蟻。木桿很細,不能同時通過兩隻螞蟻。開始時,螞蟻頭朝向任意 左或右 它們只會朝前或掉頭,不會後退。當任意兩隻螞蟻碰頭是,兩隻螞蟻會同時調頭朝反向走。假設螞蟻每秒走1厘公尺,編寫程式,求所有...

螞蟻爬桿問題

問題描述 有一根27厘公尺長的細木桿,在第3厘公尺,7厘公尺,11厘公尺,17厘公尺,23厘公尺這五個位置上各有乙隻螞蟻,木桿很細,不能同時通過兩隻螞蟻,開始時,螞蟻的頭朝向左還是右是任意的,他們只會朝前走或掉頭,但不會後退,當兩隻螞蟻相遇後,螞蟻會同時掉頭朝反方向走,假設螞蟻們每秒鐘可以走1厘公尺...

螞蟻爬桿問題

告訴你有乙個長為l l 10000 的木桿,上面有n n 1000 個螞蟻,他們一開始的朝向隨機,他們會以一定的速度一直向當前方向走,直到兩隻螞蟻相遇,他們會立即掉頭 即往相反方向走 計算t秒後螞蟻的位置 輸入的第一行為陣列個數,每組陣列第一行為三個正整數,l,t,n,分別表示長度,時間,螞蟻個數。...