Josephus Problem(约瑟夫问题)4种不同的解法

题目:

题目描述

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


n很大,直接模拟题意不行,请找出规律。

输入

 不超过1000组数据。

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

输出

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

样例输入

7
2

样例输出

7
1

第一种模拟:

#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;

int main()
{
    int n,a[1005];
    while(scanf("%d",&n)==1)
    {
        int flag=1,i=1;
        int nn=n;
        n--;
        memset(a,0,sizeof(a));
        while(n)
        {
            if(a[i]==0)
            {
                if(flag==1)
                {
                    i++;
                    if(i>nn)
                        i=1;
                    flag=2;
                }
                else
                {
                    a[i]=1;
                    flag=1;
                    i++;
                    if(i>nn)
                        i=1;
                    n--;
                }
            }
            else
            {
                i++;
                if(i>nn)
                    i=1;
            }
        }
    for(int i=1;i<=nn;i++)
        if(a[i]==0)
        printf("%d\n",i);
    }
    return 0;
}

特点:简单,当n大时会超时

第二种找规律:

利用递推可以发现规律

f(1)=1;
N为偶数:f(N)=2*f(N/2)+1;

N为奇数:f(N)=2*f((N-1)/2)+1;

f(n)表示的是n个人最后留下的人的序号

int f(int n)
{
    if(n==1)
        return 1;
    else if(n&1)
        return 2*f1((n-1)/2)+1;
    else
        return 2*f1(n/2)-1;
}

特点:需要多列几项找规律,运行速度较快

第三种找规律:

N
1    2     3     4    5     6     7     8    9     10    11    12    13    14    15    16
F(N)
1     1     3     1     3     5     7     1     3     5     7     9     11    13    15    1

通过观察我们能够发现,f(N)是一个基数的循环,以2^m为开头,即:

如果N=2^m+L,(0<=L<2^m),那么f(N)=2*L+1;

int f(int n)
{
    int pos=1;
    int temp=1;
    for(int i=0; i<31; i++)
    {
        if(n&pos)// if(n<=pos)
            temp=pos;// 得到不大于n的2^temp次幂
        pos=(pos<<1);  //pos*=2;
    }
    return ((n-temp)<<1)+1;
}

特点:有点难发现其中规律,利用位运算可以加快速度

第四种找规律:

题目中的数数‘一二’,换成了数m个数

题目描述

n个人排成一圈,按顺时针方向依次编号1,2,3…n。从编号为1的人开始顺时针"一二三...."报数,报到m的人退出圈子。这样不断循环

下去,圈子里的人将不断减少。最终一定会剩下一个人。试问最后剩下的人的编号。


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

输入

 超过1000组数据。

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

利用数学归纳可以得到:

 f(1) = 0;
f(n) = (f(n - 1) + m) % n;

再优化一下

代码来自:https://blog.csdn.net/feng_zhiyu/article/details/73431094 

#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long LL;
LL solve(LL n,LL m,LL s)
{
    if(m==1) return (n-1+s)%n;
    LL ans=0,i=2;
    while(i<=n)
    {
        if(ans+m<i)///递增时,x为增加次数
        {
            LL x=(i-ans-1)%(m-1)?(i-ans-1)/(m-1):(i-ans-1)/(m-1)-1;
            if(i+x>n)
            {
                ans+=(n+1-i)*m;
                break;
            }
            i+=x;
            ans+=x*m;
        }
        else  ///ans不是递增时
        {
            ans=(ans+m)%i;///普通求法
            i++;
        }
    }
    return (ans+s)%n;
}
int main()
{
    LL n,m;
    while(scanf("%lld%lld",&n,&m)!=EOF)
    {
        LL ans=solve(n,m,1);
        printf("%lld\n",ans?ans:n);
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_40733911/article/details/80809012