约瑟夫问题

约瑟夫问题大意:有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相差不大时,这里的优化所剩无几.
总之,我们在这里就算解决了一个比较小的约瑟夫问题.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值