問題描述: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;
}