約瑟夫環 數學解法

2021-06-29 06:57:12 字數 1831 閱讀 8403

約瑟夫環是乙個數學的應用問題:已知n個人(以編號1,2,3…n分別表示)圍坐在一張圓桌周圍;從編號為k的人開始報數,數到m的那個人出列;他的下乙個人又從1開始報數,數到m的那個人又出列;依此規律重複下去,直到圓桌周圍的人全部出列。

f[1]=0; f[i]=(f[i-1]+m)%i; (i>1)

#includeusing namespace std;

int fun(int n, int m)

void main()

#include int josephus(int n, int m) 

else

}int main()

scanf("%d", &m);

int result = josephus(n, m);

printf("%d\n", result+1);

} return 0;

}

公式1的推導:——————————

給出乙個序列,從0~n-1編號。其中,k代表出列的序號的下乙個,即k-1出列。

a0, 1, …, k-1, k, k+1, …, n-1

那麼,出列的序號是(m-1)%n,k=m%n(這個可真的是顯而易見)。出列k-1後,序列變為

b0, 1, …, k-2, k, k+1, …, n-1

然後,我們繼續從n-1後延長這個序列,可以得到

c`0, 1, …, k-2, k, k+1, …, n-1, n, n+1, …, n+k-2

我們取從k開始直到n+k-2這段序列。其實這段序列可以看作將序列b的0~k-2段移到了b序列的後面。這樣,得到乙個新的序列

ck, k+1, …, n-1, n, n+1, …, n+k-2

好了,整個序列c都減除乙個k,得到

d0, 1, …, n-2

c序列中的n-1, n, n+1都減除個k是什麼?這個不需要關心,反正c序列是連續的,我們知道了頭和尾,就能知道d序列是什麼樣的。

這樣你看,從序列a到序列d,就是乙個n序列到n-1序列的變化,約瑟夫環可以通過遞推來獲得最終結果。ok,繼續向下。

剩下的就是根據n-1序列遞推到n序列。假設在n-1序列中,也就是序列d中,我們知道了最終剩下的乙個序號是x,那麼如果知道了x轉換到序列a中的編號x`,不就是知道了最終的結果了麼?

下面我們就開始推導出序列a中x的序號是什麼。

d->c,這個變換很容易,就是x+k;

c->b,這個變換是網上大家都一帶而過的,也是令我鬱悶的乙個關鍵點。從b->c,其實就是0~k-2這段序列轉換為n~n+k-2這段序列,那麼再翻轉回去,簡單的就是%n,即(x+k)%n。%n以後,k~n-1這段序列值不會發生變化,而n~n+k-2這段序列則變成了0~k-2;這兩段序列合起來,就是序列b。

於是乎,我們就知道了,x`=(x+k)%n。並且,k=m%n,所以x`=(x+m%n)%n=(x+m)%n。公式1就出來了:f[i]=(f[i-1]+m)%i。當然,i=1就是特殊情況了,f[1]=0。這裡還有乙個小問題。也許你會迷惑為什麼x`=(x+m%n)%n=(x+m)%n中的%n變成公式中f[i]=(f[i-1]+m)%i中的%i?其實這個稍微想想就能明了。我們%n就是為了從序列c轉換到序列b——這是在n-1序列轉換成n序列時%n;那麼從n-2轉換到n-1呢?不是要%(n-1)了嗎?所以這個值是變數,不是常量。

好了,這個最後需要注意的就是從一開始,我們將n序列從0~n-1編號,所以依據公式1得出的序號是基於0開始的。

約瑟夫環數學解法

無論是用鍊錶實現還是用陣列實現都有乙個共同點 要模擬整個遊戲過程,不僅程式寫起來比較煩,而且時間複雜度高達o nm 當n,m非常大 例如上百萬,上千萬 的時候,幾乎是沒有辦法在短時間內出結果的。我們注意到原問題僅僅是要求出最後的勝利者的序號,而不是要讀者模擬整個過程。因此如果要追求效率,就要打破常規...

約瑟夫環的數學解法

約瑟夫環問題是一道經典的資料結構題目 問題描述 n個人 編號0 n 1 從0開始報數,報到 m 1 的退出,剩下的人繼續從0開始報數。求勝利者的編號。一般我們採用乙個迴圈佇列來模擬約瑟夫環的求解過程,但是如果n比較大的時候,採用模擬的方式求解,需要大量的時間來模擬退出的過程,而且由於需要占用大量的記...

約瑟夫環的數學解法

約瑟夫環問題是一道經典的資料結構題目 問題描述 n個人 編號0 n 1 從0開始報數,報到 m 1 的退出,剩下的人繼續從0開始報數。求勝利者的編號 一般我們採用乙個迴圈佇列來模擬約瑟夫環的求解過程,但是如果n比較大的時候,採用模擬的方式求解,需要大量的時間來模擬退出的過程,而且由於需要占用大量的記...