# 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;
}
```