---
# System prepended metadata

title: Josephus Problem

---

# Josephus Problem

有n個人圍成一個圈，一個一個處決，最後剩下的一個人會被放走。
假設每m個人殺一次，請問誰會被放走?
例如有5個人，編號1~5，每隔2人殺一個，處決順序會是2、4、1、5，最後放走3
那給定n跟m，如何決定幾號可以存活?

**想法1**
可以想到每次處刑人都是一圈一圈的繞來決定要殺誰
本來n人，每走完一圈會剩下n-floor(n/m)人
將每個人重新從1編號到n-floor(n/m)
可以想像本來問題是$T(n,m)$，可以有以下關系
$T(n,m)=T(n-floor(n/m),m)$
至於怎麼幫每個人重新編號呢?
由於在每一圈最後一個被殺的人的下一位會是下一圈的1號
可以歸納出以下的關係，目前這一圈的位置為$pos$，下一圈為$Newpos$
$Newpos=pos-m*floor(n/m)$ ，由於$Newpos$一定要大於0，如果此結果不是正整數，那$Newpos=pos-m*floor(n/m)+n-floor(pos/m)$

其實不難理解，$m*floor(n/m)$就是這圈最後一個被殺的人的編號，只需要將這圈一開始的人重新編號就可以，一開始是1~n，將第$m*floor(n/m)$編號為0，本來$m*floor(n/m)+1$是新的1號，原本的$pos$會變成$pos+n-m*floor(n/m)$號
而把這圈該殺的人殺完後$pos$前面的人被殺了$floor(pos/m)$個
所以結果就是$Newpos=pos-m*floor(n/m)+n-floor(pos/m)$

值得注意的是如果n<m該怎麼處理?
n<m代表每次只會有一個人被殺(原本編號加上m後能被m整除的那個人)，然後再重新編號，比這個被殺的人編號大的人編號減一，比它小的人不用變化。

依照以下的code可以對每個編號做檢查，如果此編號的人最後一圈能活下來就完成此題目。
時間複雜度$O(nm)$

```cpp=
#include <iostream>
#include <string>
#include <cstring>

using namespace std;

#define SIZE 101

bool Joseph_Problem(int n,int m,int pos){
    if(pos%m==0)
        return false;
    if(n==1)
        return true;
    if(n<m){
        if((pos+n)%m==0)
            return false;
        else if(pos+n-m<0)
            return Joseph_Problem(n-1,m,pos);
        else
            return Joseph_Problem(n-1,m,pos+n-m);
    }
    
    int new_size=n-(n/m),new_pos;
    if(pos-(m*(n/m))>0)
        new_pos=pos-(m*(n/m));
    else
        new_pos=pos-(m*(n/m))+n-(pos/m);
    return Joseph_Problem(new_size,m,new_pos);
}


int main(){

    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    bool Turn_off[SIZE],Done;

    int N,m,i,count;
    cin>>N>>m;
    for(int i=1;i<=N;i++){
        if(Joseph_Problem(N,m,i))
            cout<<i<<" Survive\n";
        else
            cout<<i<<" Killed\n";
    }
    return 0;
}

```

**想法2**
從上面的思考可以發現，這個問題就是先將整體的人找到正確的編號，再不斷轉換問題大小
所以問題大小n的時候可以思考為先殺一個人(編號m的人)變成n-1人再轉換所有人編號
轉的方式如下，假設原本n人的時候在位置i的人，轉換過後會是
$i=i-m$ if $i-m>0$
$i=i+(n-1)-m$ if $i-m<0$
而我們知道最後會活下來的人就是在1個人的時候編號1的那個人
透過上述可以得到以下遞迴式
$T(n,m)=(T(n-1,m)+(m-1))\%(n)+1$
**第j個位置的人往回推要先加(m-1)人，因為先前有一人被殺，所以少一個，之後mod n，得到的位置0的人其實是位置1的，所以再加1**

並且要從$T(1,m)=1$的人往回推
所以可以寫成以下的code
時間複雜度是$O(n)$
```cpp=
#include <iostream>
#include <string>
#include <cstring>

using namespace std;

#define SIZE 101

int Joseph_Problem(int n,int m){
    if(n==1)
        return 1;
    else
        return ((Joseph_Problem(n-1,m)+m-1)%n) + 1;
}


int main(){

    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    bool Turn_off[SIZE],Done;

    int N,m,i,count;
    N=7,m=3;
    cout<<Joseph_Problem(N,m)<<endl;
    return 0;
}

```


**想法3**
有遞迴式，那就可以用dp解
建立一個大小n+1的array，使用1到n的位置，arrary[i]代表總人數i的時候的編號
時間複雜度和遞迴實作一樣是$O(n)$，但沒有遞迴的overhead，在實作上更優。
**初始值:** $Array[1]=1$
**遞迴式:** $Arrary[i]=(Array[i-1]+m-1)\%i + 1$
**解答:** $Array[n]$
**code:**
```cpp=
#include <iostream>
#include <string>
#include <cstring>

using namespace std;

#define SIZE 101

int Joseph_Problem_DP(const int n,int& m){
    int Pos[n+1];
    Pos[1]=1;
    for(int i=2;i<=n;i++)
        Pos[i]=(Pos[i-1]+(m-1))%i + 1;

    return Pos[n];
}

int main(){

    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    bool Turn_off[SIZE],Done;

    int N,m,i,count;
    N=7,m=3;
    cout<<Joseph_Problem_DP(N,m)<<endl;
    return 0;
}
```