约瑟夫环问题

一般约瑟夫环问题:N个人坐成一个圆环(编号为1 - N),从第1个人开始报数,数到K的人出列,后面的人重新从1开始报数。问最后剩下的人的编号。例如:N = 3,K = 2。2号先出列,然后是1号,最后剩下的是3号。


实际上对于约瑟夫环问题,最常见的有2种解法。


第一种就是直接暴力模拟链表,当然这种做法的时间复杂度很高,而且实现起来还很麻烦。第二种方法就是利用递推关系,有f[1] = 0,f[i] = (f[i-1] + K) % i;一个递推就完成,时间复杂度为O(n)。

#include <iostream>
#include <string.h>
#include <stdio.h>

using namespace std;

int main()
{
    int n,k;
    while(cin>>n>>k)
    {
        int ans = 0;
        for(int i=2;i<=n;i++)
            ans = (ans + k) % i;
        cout<<ans+1<<endl;
    }
    return 0;
}

HDU 2211

题意:对于约瑟夫环问题,我们一般求的是最后一个幸存者,而本题题意是求最后一个被杀者的编号。

分析:设f(N,K)表示N个人每第K个出列的最后取出的编号。那么f(N,K)进行第一次选取后,剩下N-N/K个人,这剩下的人里最后取出的编号为f(N-N/K,K),简记为x,那么它在前一次队列里的编号为x+(x-1)/(K-1),所以f(N,K)=(x-1)/(K-1)+x,其中x=f(N-N/K,K)。

#include <iostream>
#include <string.h>
#include <stdio.h>

using namespace std;

int Work(int n,int k)
{
    if(n == k) return k;
    int t = Work(n-n/k,k);
    return (t-1)/(k-1) + t;
}

int main()
{
    int T,N,K;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&N,&K);
        printf("%d\n",Work(N,K));
    }
    return 0;
}

好了,现在我们来看一下约瑟夫环问题的升级版:

N个人坐成一个圆环(编号为1 - N),从第K个人开始报数,数到M的人出列,后面的人重新从1开始报数。问最后剩下的人的编号。例如:N = 3,M = 2,K = 1。2号先出列,然后是1号,最后剩下的是3号。


对于这个问题,我们同样有递推求解方法:

LL Josephus(LL n,LL m,LL k) //参数分别为:人数,步长,起始报数位置
{
    for(LL i=1; i<=n; i++)
        k = (k + m - 1) % i + 1;
    return k;
}
这个算法的时间复杂度为O(n)。


事实上,如果我们观察上述算法中的变量k,他的初始值为第一个出圈人的编号,但在循环的过程中,我们会发现它常常处在一种等差递增的状态,我来看这个式子:k = (k + m - 1) % i +  1,可以看出,当i比较大而k+m-1比较小的时候,k就处于一种等差递增的状态,这个等差递增的过程并不是必须的,可以跳过。

我们设一中间变量 x,列出等式:k + m * x – 1 = i + x,解出x,令k = k + m * x,将i + x直接赋值给i,这样就跳过了中间共x重的循环,从而节省了等差递增的时间开销。可是其中求出来的x + i可能会超过n,这样的结果事实上已经告诉我们此时可以直接结束算法了,即:


k = k + m * (n - i) ;
i = n;

结 束。
另外对于m = 1的情况可以单独讨论:
当k == 1时,最终结果就是n;
当k != 1时,最终结果就是(k + n -  1) % n。


HDU 3089

#include <iostream>
#include <string.h>
#include <stdio.h>

using namespace std;
typedef long long LL;

LL Work(LL n,LL m,LL k)
{
    if(m == 1)
        k = (k == 1 ? n:(n+k-1)%n);
    else
    {
        for(LL i=1; i<=n; i++)
        {
            if(k + m < i)
            {
                LL x = (i-k+1)/(m-1) - 1;
                if(i + x < n)
                {
                    i += x;
                    k += m*x;
                }
                else
                {
                    k += m*(n-i);
                    i = n;
                }
            }
            k = (k+m-1)%i+1;
        }
    }
    return k;
}

int main()
{
    LL n,k;
    while(cin>>n>>k)
        cout<<Work(n,k,1)<<endl;;
    return 0;
}



  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值