問題描述:n個人(編號0~(n-1)),從0開始報數,報到(m-1)的退出,剩下的人繼續從0開始報數。求勝利者的編號。
我們知道第一個人(編號一定是(m-1) mod n) 出列之後,剩下的n-1個人組成了一個新的約瑟夫環(以編號為k=m mod n的人開始):
k k+1 k+2 ... n-2,n-1,0,1,2,... k-2
並且從k開始報0。
現在我們把他們的編號做一下轉換:
k --> 0
k+1 --> 1
k+2 --> 2
...
...
k-2 --> n-2
變換後就完完全全成為了(n-1)個人報數的子問題,假如我們知道這個子問題的解:例如x是最終的勝利者,那麼根據上面這個表把這個x變回去不剛好就是n個人情況的解嗎?!!變回去的公式很簡單,相信大家都可以推出來:x'=(x+k) mod n
如何知道(n-1)個人報數的問題的解?對,只要知道(n-2)個人的解就行了。(n-2)個人的解呢?當然是先求(n-3)的情況 ---- 這顯然就是一個倒推問題!好了,思路出來了,下面寫遞推公式:
令f表示i個人玩游戲報m退出最後勝利者的編號,最後的結果自然是f[n]
遞推公式
f[1]=0;
f=(f+m) mod i; (i>1)
有了這個公式,我們要做的就是從1-n順序算出f的數值,最後結果是f[n]。因為實際生活中編號總是從1開始,我們輸出f[n]+1
約瑟夫問題的相關子問題。
1.Poj 3517 And Then There Was One
題意:固定開始點不是1,而是m。
先去掉一個數,轉換成n-1個數的約瑟夫環問題,再將最後結果s=(m+s)%n+1即可.
#include <stdio.h> #include <stdlib.h> #include <algorithm> #include <string.h> #include <math.h> #include <iostream> using namespace std; #define Maxn 10100 int f[Maxn]; int main() { #ifndef ONLINE_JUDGE freopen("in.txt","r",stdin); #endif int n,m,k; while(scanf(" %d %d %d",&n,&k,&m)!=EOF) { if(n == 0 && m == 0 && k == 0) break; f[1] = 0; for(int i=2;i<=n;i++) { f[i] = (f[i-1] + k)%i; } printf("%d\n",(f[n-1]+m)%n + 1); } return 0; } #include <stdio.h> #include <stdlib.h> #include <algorithm> #include <string.h> #include <math.h> #include <iostream> using namespace std; #define Maxn 10100 int f[Maxn]; int main() { #ifndef ONLINE_JUDGE freopen("in.txt","r",stdin); #endif int n,m,k; while(scanf(" %d %d %d",&n,&k,&m)!=EOF) { if(n == 0 && m == 0 && k == 0) break; f[1] = 0; for(int i=2;i<=n;i++) { f[i] = (f[i-1] + k)%i; } printf("%d\n",(f[n-1]+m)%n + 1); } return 0; }
2.Hoj 1016 Joseph's problem I
每次的間隔是是質數。我們只要預處理篩一次區間范圍內的質數即可。
?#include <stdio.h> #include <stdlib.h> #include <algorithm> #include <string.h> #include <math.h> #include <iostream> using namespace std; #define Maxn 50000 int prime[Maxn]; int vis[Maxn]; int get_Prime(int n) { memset(vis,0,sizeof(vis)); int np = 0; for(int i=2;i<=n;i++) { if(!vis[i]) prime[np++] = i; long long t; for(int j=0;j<np && (t = prime[j]*i)<=n;j++) { vis[t] = 1; if(i%prime[j] == 0) break; } } } int f[Maxn]; int main() { #ifndef ONLINE_JUDGE freopen("in.txt","r",stdin); #endif get_Prime(Maxn); int n; while(scanf(" %d",&n)!=EOF && n!=0) { f[1] = 0; for(int i=n-2;i>=0;i--) { f[n-i] = (f[n-i-1] + prime[i]) % (n - i); } printf("%d\n",f[n]+1); } return 0; } #include <stdio.h> #include <stdlib.h> #include <algorithm> #include <string.h> #include <math.h> #include <iostream> using namespace std; #define Maxn 50000 int prime[Maxn]; int vis[Maxn]; int get_Prime(int n) { memset(vis,0,sizeof(vis)); int np = 0; for(int i=2;i<=n;i++) { if(!vis[i]) prime[np++] = i; long long t; for(int j=0;j<np && (t = prime[j]*i)<=n;j++) { vis[t] = 1; if(i%prime[j] == 0) break; } } } int f[Maxn]; int main() { #ifndef ONLINE_JUDGE freopen("in.txt","r",stdin); #endif get_Prime(Maxn); int n; while(scanf(" %d",&n)!=EOF && n!=0) { f[1] = 0; for(int i=n-2;i>=0;i--) { f[n-i] = (f[n-i-1] + prime[i]) % (n - i); } printf("%d\n",f[n]+1); } return 0; } 3.Hoj 1107 Joseph's problem II
k個good guys ,k個bad guys,每次不能殺掉good guy,問最小的m.
解題方法:類似於數組模擬。每次變更start 和end的范圍。每次殺掉一個人。下一個的編號變為0。
include <stdio.h> #include <stdlib.h> #include <algorithm> #include <string.h> #include <math.h> #include <iostream> using namespace std; #define Maxn 10100 int f[15]; bool solve(int k,int m) { int start = 0,end = k - 1; bool flag = true; for(int i=2*k;i>k;i--) { int kill = (m-1)%i; if(kill>=start && kill<=end) { flag = false; break; } start = ((start - m)%i + i)%i; end = ((end - m)%i + i)%i; } return flag; } void init() { for(int k=1;k<14;k++) { for(int m=k+1;;m++) { if(solve(k,m)) { f[k] = m; break; } } } } int main() { #ifndef ONLINE_JUDGE freopen("in.txt","r",stdin); #endif init(); int k; while(scanf(" %d",&k)!=EOF && k!=0) { printf("%d\n",f[k]); } return 0; } #include <stdio.h> #include <stdlib.h> #include <algorithm> #include <string.h> #include <math.h> #include <iostream> using namespace std; #define Maxn 10100 int f[15]; bool solve(int k,int m) { int start = 0,end = k - 1; bool flag = true; for(int i=2*k;i>k;i--) { int kill = (m-1)%i; if(kill>=start && kill<=end) { flag = false; break; } start = ((start - m)%i + i)%i; end = ((end - m)%i + i)%i; } return flag; } void init() { for(int k=1;k<14;k++) { for(int m=k+1;;m++) { if(solve(k,m)) { f[k] = m; break; } } } } int main() { #ifndef ONLINE_JUDGE freopen("in.txt","r",stdin); #endif init(); int k; while(scanf(" %d",&k)!=EOF && k!=0) { printf("%d\n",f[k]); } return 0; }