约瑟夫问题大意:有N个人站成一圈,从第一个人开始报数,从1开始报,若报到M的倍数的人,就必须离开游戏.问最后剩下的胜利的人是谁.
首先我们可以很快想到去模拟游戏的过程,并打出如下代码:
#include<cstdio>
bool mark[30005];
int main(){
int n,m;
scanf("%d %d",&n,&m);
int now=1,last=1,cnt=0;
while(cnt<n){
for(int i=1;i<=m;i++){
while(mark[now]){
now++;
if(now==n+1)now-=n;
}
last=now;
now++;
if(now==n+1)now-=n;
}
mark[last]=1;
printf("%d ",last);
cnt++;
}
return 0;
}
可是这样的复杂度太高了,高达O(NM),如果N,M过大的话无法在规定时间内求得答案.
那我们就衍生出了一种新的解法:在现在,我们可以知道下一个出局的人是谁,那么如果知道在
(n-1)个人的情况下出局的人是谁,就能递归地求出最后赢的人是谁.
递归版代码如下:
#include<cstdio>
int m;
int solve(int x){
if(x==1)return 1;
if(x==2)return m%2+1;
return (solve(x-1)+m-1)%x+1;
}
int main(){
int n;
scanf("%d %d",&n,&m);
printf("%d\n",solve(n));
return 0;
}
循环版代码如下:
#include<cstdio>
int m;
int main(){
int n;
scanf("%d %d",&n,&m);
int s=1;
for(int i=2;i<=n;i++)s=(s+m-1)%i+1;
printf("%d\n",s);
return 0;
}
这样我们能把复杂度降到O(N),能够应付很多情况了.
但我们可以发现,如果N,M相差很大,循环中的s一直都在递增.
那么如果我们可以求出这个递增的次数,并直接加上它,时循环的次数大大减少.
可这又怎么求呢?
我们可以知道 s+m*x-1>=i+x 时,s会变少,那么直接把这个x求出来就好了.
代码如下:
#include<cstdio>
int n,m;
int Josephus(int s){
if(m==1)return s==1?n:s-1;
for(int i=2;i<=n;i++){
if(s+m<=i){
int x=(i-s+1)/(m-1);
if(i+x>n)x=n-i+1,i=n;
else i+=x-1;
s=(s+x*m-1)%i+1;
}
s=(s+m-1)%i+1;
}
return s;
}
int main(){
scanf("%d %d",&n,&m);
printf("%d\n",Josephus(1));
return 0;
}
至此,我们可以知道,如果N,M相差足够大的话,上述方法将获得很大的作用,而当N,M相差不大时,这里的优化所剩无几.
总之,我们在这里就算解决了一个比较小的约瑟夫问题.