问题 E: 约瑟夫问题再优化详细过程

题目描述
n个人排成一圈,按顺时针方向依次编号1,2,3…n。从编号为1的人开始顺时针"一二三…"报数,报到m的人退出圈子。这样不断循环下去,圈子里的人将不断减少。最终一定会剩下一个人。试问最后剩下的人的编号。

本题的数据规模更具有挑战性,尝试更通用且高效的算法。

输入
不超过1000组数据。

每组数据一行,每行两个正整数,代表人数n (1 <= n < 231)和m(1<=m<=1000)。

输出
每组输入数据输出一行, 仅包含一个整数,代表最后剩下的人的编号。
样例输入 Copy
7 2
2 2
样例输出 Copy
7
1

约瑟夫问题公式推导过程
我们已经知道有一个约瑟夫问题求解公式a=(a+m)%k(a是出去的编号,m是报数号,k是上面报数后剩下的人数;注意,这里标号是从0开始的,最后结果要加1)
接下来是算法再优化:
可以发现,当a+m<k时,得出来的a=(a+m)%k还是a+m(比如(1+2)%10=(1+2)),继续(a+m)+m%(k+1)【为什么是k+1呢?看这个在这里插入图片描述
我们的k就相当于上面代码的i,m相当于s=(s+2)%i中的2,只是这个是1,2报数】,只要%左边小于右边,算出来的数与左边相等,依此类推,我们可以扩展为x个m相加,有a+mx<k+(x-1),解出x<(k-1-a)/(m-1),当能整除时,取x=(k-1-a)/(m-1)-1,不能整除时取x=(k-1-a)/(m-1)【取整数部分,满足该不等式】,那么什么时候退出循环?当k+x>n时即可退出【x是报的m的个数,报到一个m k就加1】因为k+x>n,x和n都为整数,且x是1,2,3,……这样依次加1递增的,所以就有k+x-1=n,得出x=n-(k-1),带入公式a=(a+mx)%n【为什么这个时候是对n取余了呢?因为此时循环到达最后了,人数为n】得a=(a+(n-(k-1))*m)%n,退出;
当a+m>k时,按原公式a=(a+m)%k求解

【当时我理解这个东西理解了好久啊,最后在npy的帮助讲解下,终于懂了那么一丢丢,在这里感谢一下ta喽,虽然可能看不见,哈哈哈哈,我写的可能不是很清楚,仅供参考,欢迎批评指正~~~~~ _

代码如下:

#include<bits/stdc++.h>
using namespace std;
int main(){
    long long m,n;
    while(scanf("%lld%lld",&n,&m)!=EOF){
        long long a=0,k,x;
        if(m==1){
            cout<<n<<endl;
            continue;
        }
        else{
        for(k=2;k<=n;){
        if(a+m<k){//当a+m<k时可以减少时间复杂度 
            if((k-1-a)%(m-1)==0){//x不能是m-1的倍数 
                x=(k-1-a)/(m-1)-1;
            }
            else x=(k-1-a)/(m-1);//a+m*x<k+x-1变换而来 
            if(k+x>n){//k+x>n ---->k+x-1==n---->x=n-(k-1)
                a=(a+(n-(k-1))*m)%n;
                break;//当还有n个人时的情况已找到,退出循环 
            }
                k=k+x;
                a=(a+x*m)%k;
        }
            else{
                a=(a+m)%k;
                k++;
            }
             
        } }
        cout<<a+1<<endl;
    }
    return 0;
}   
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值