1. 程式人生 > >約瑟夫環遞迴思想

約瑟夫環遞迴思想

轉自:開啟連結

題目描述:30個遊客同乘一條船,因為嚴重超載, 加上風浪大作,危險萬分。因此船長告訴乘客,只有將全船 一半的旅客投入海中,其餘人才能倖免於難。無奈,大家只 得同意這種辦法,並議定30 個人圍成一圈,由第一個人數起,依次報數,數到第9人,便把他投入大海中,然後再從 他的下一個人數起,數到第9人,再將他投入大海中,如此 迴圈地進行,直到剩下 15 個遊客為止。問:哪些位置是將 被扔下大海的位置?

這道題目的難點在於30個人會圍成一個圈

舉個例子有十個人,報數到四的時候我們把他丟下去

開始時:0 1 2 3 4 5 6 7 8 9

第一次:0 1 2    4 5 6 7 8 9

第二次:0 1 2    4 5 6    8 9

第三次:0    2    4 5 6    8 9

....

...

最終會留下編號為4的人.

拿開始時和第一次進行分析:

第一次這些編號已經不能組成一個環,但是可以看出4至2之間還是連著的(4 5 6 7 8 9 0 1 2),且下一次報數將從4開始。但是,之後的報數將總要考慮原編號3處的空位問題。

如何才能避免已經產生的空位對報數所造成的影響呢?

可以將剩下的9個連續的陣列成一個新的環(將2、4連線),這樣報數的時候就不用在意3的空位了。但是新產生的環的數字並非連續的,報數時不像之前那樣好處理了(之前沒人被扔海里時下一個報數的人的編號可以遞推,即(當前編號+1)%sum ),無法不借助儲存結構得知下一個應該報數的現存人員編號。

如何使新環上的編號能夠遞推來簡化我們之後的處理呢?

可以建立一種有確定規則的對映,要求對映之後的數字可以遞推,且可以將在新環中繼續按原規則報數得到的結果逆推出在舊環中的對應數字。

方法:將它與  sum-1 個人組成的(0 ~ sum-1)環一 一對映。

比如之前的栗子,將剩餘的 9 人與  9 人環(0~8)一 一對映。既然 3 被扔到海里之後,報數要從4開始 (4 其實在數值上等於最大報數值),那麼就將4對映到0~8的新環中0的位置,也就是說在新環中從0開始報數即可,且新環中沒有與3對應的數字,因此不必擔心有空位的問題。從舊環的 4 開始報數等效於從新環中的 0 開始報數。

開始時:0 1 2 3 4 5 6 7

8 9

第一次: 6 7 8   0 1 2 3 4 5

如何由新環中的 3 得到舊環中的 7 呢。其實可以簡單地逆推回去 : 新環是由  (上一次編號-最大報數值)%總人數  得到的,所以逆推時可以由 ( 下一次數字 + 最大報數值 )% 上一次總人數 取得。

如 : ( 3 + 4 ) % 10 =7 .

也就是說在,原序列( sum ) 中第二次被扔入海中編號可以由新序列( sum - 1) 第一次扔海里的編號通過特定的逆推運算得出。

而新序列 (sum -1)也是(從0開始)連續的,它的第二次被扔入海中的編號由可以由(sum - 2)的第一次扔入海里的編號通過特定的逆推運算得出,並且它的第二次被扔入海中的編號又與原序列中的第三次被扔入海里的編號是有對應關係的。

也求是說有以下推出關係:

(sum-2)環的第1次出環編號 >>>(sum-1)環的第2次出環編號 >>>(sum)環的第3次出環編號

即 在以 k 為出環報數值的約瑟夫環中, m人環中的第n次出環編號可以由 (m-1) 人環中的第 (n-1) 次出環編號通過特定運算推

#include <stdio.h>
#include <math.h>
#include <iostream> 
       
int ysfdg(int sum,int value,int n)
{
    if(n==1)
        return (sum+value-1)%sum;
    else
        return (ysfdg(sum-1,value,n-1)+value)%sum;
}

int main()
{
    int sum=0,count=0,alive=0,i=0;

    //讀入總人數,報數值,存活人數
   cin>>sum>>count>>alive;
    for(i=1;i<=sum-alive;i++)
        printf("第%2d個被扔海里人的編號:%2d\n",i,ysfdg(sum,count,i)+1);

    return 0;
}

 實際編號是從1開始,而不是0,輸出時要注意轉換。