約瑟夫環問題:圓桌報數問題
約瑟夫環問題:一圈共有N個人,開始報數,報到M的人自殺,然後重新開始報數,問最後自殺的人是誰?
如圖:內環表示人排列的環,外環表示自殺順序;上面N=41,M=3。
最普通辦法就是模擬整個過程:建一個bool陣列,true表示此人還活著,false表示已經自殺。可以模擬整個過程
- #include<iostream>
- usingnamespace std;
- int main()
- {
- int N;//人的總個數
- int M;//間隔多少個人
- cin>>N;
- cin>>M;
-
bool *p=new
- for (int i=1; i <= N; i++)
- *(p+i)=true;
- int count=0;//統計自殺的人數
- for (int i=1, j=0; ;i++)//i用來表示迴圈,j用來計算是不是第N個人
- {
- if (*(p+i))//此人還活著
- {
- j++;
- if (j == M)
- {
- *(p+i)=false;
-
j=0;
- count++;//統計自殺的人
- }
- if (count == N)
- {
- cout<<"最後自殺的人是:"<<i<<endl;
- break;
- }
- }
- if(i == N)
- i=0;
- }
- delete []p;
- return 0;
- }
把問題重新描述一下:N個人(編號0~(N-1)),從0開始報數,報到(M-1)的自殺,剩下的人繼續從0開始報數。求最後自殺者的編號。
N個人編號如下:
第一個自殺的人是(M-1)%N,例如上圖中,41個人中,報到3的人自殺,則字一個自殺的人的編號是(3-1)%41=2。編號(M-1)%N自殺後,剩下的人排列如下:
有人自殺後,下一個位置M又從零開始報數,因此環應該如下:
將上面的排列順序重新編號:
問題變為(N-1)個人,報到為(M-1)的人自殺,問題規模減小了。
我們定義F(N-1)為總數為N-1個人時,最後自殺的人的序號。根據上面的對比,F(N-1)可以看成是N個人自殺掉一個之後重新編碼組成的新問題,與重新編碼之前的序號對比多了一個M,那麼F(N-1) + M就相當於是N個人自殺掉一個之後,接著繼續自殺到最後一個的序號。即F(N) = [F(N-1) + M]%N.
這樣一直進行下去,最後剩下編號為0的人。用函式表示:
F(1)=0
當有2個人的時候(N=2),報道(M-1)的人自殺,最後自殺的人是誰?應該是在只有一個人時,報數時得到的最後自殺的序號加上M,因為報到M-1的人已經自殺,只剩下2個人,另一個自殺者就是最後自殺者,用函式表示:
F(2)=F(1)+M
可以得到遞推公式:
F(i)=F(i-1)+M
因為可能會超出總人數範圍,所以要求模
F(i)=(F(i-1)+M)%i
有了遞推公式就可以在O(N)時間求出結果:
- #include<iostream>
- usingnamespace std;
- int main()
- {
- int N;//人的總個數
- int M;//間隔多少個人
- cin>>N;
- cin>>M;
- int result=0;//N=1情況
- for (int i=2; i<=N; i++)
- {
- result=(result+M)%i;
- }
- cout<<"最後自殺的人是:"<<result+1<<endl;//result要加1
- return 0;
- }