约瑟夫环问题

约瑟夫环

https://ac.nowcoder.com/acm/contest/19306/1003

前言:

这道题经过苦思冥想,以为可以解决的,没想到当人数n=5时,一直输不出来,一直TLE,没搞明白。

题目:

链接:https://ac.nowcoder.com/acm/contest/19306/1003
来源:牛客网

时间限制:C/C++ 1秒,其他语言2秒
空间限制:C/C++ 32768K,其他语言65536K
64bit IO Format: %lld

题目描述

n个人(0,1,2,3,4...n-1),围成一圈,从编号为k的人开始报数,报数报到m的人出队(报数是1,2,...m这样报的)。下次从出队的人之后开始重新报数,循环往复,当队伍中只剩最后一个人的时候,那个人就是大王。现在,给定n,k,m,
请你求出大王的编号。

输入描述:

输入一行包含三个整数n,k,m
1<=n<=100,1<=k<=n-1,1<=m<=100

输出描述:

输出一个整数
示例1

输入

复制 5 1 2
5 1 2

输出

复制 3
3

分析:

引用CSDN的一位博主对这个题的解析,https://blog.csdn.net/tingyun_say/article/details/52343897,下面的分析和一些代码、图片,都是引用这位博主的。
我写的只是我我看完这个分析的理解。
首先,一共n个人,分别为1,2,3,到n号。喊数字从1开始数,数到q停。
当q等于2时,可以特判,所以可以分为两种情况。

<1>q=2时,分两种情况。

    1.  当人数n刚好为时,如:2,4,6,8......等等

n=2时,2没了,剩下1
n=4时,第一次去掉2,4,剩下1,3,接下来去掉了3,剩下1
n=8时,第一次去掉2,4,6,8,剩下1,3,5,7,第二次去掉3,7,剩下1,5,第三次去掉5,剩下1
F(n) = 2^k – 2^k-1 = 2^k-1     				n=2^k
F(n) = 2^k-1 – 2^k-2 = 2^k-2    			        n=2^k-1
......
F(2^2) = 2^2 – 2^1 = 2^1 					n=2^2
相当于,每次淘汰的时候,都会去掉一半人,所以从1开始的,最后肯定剩下了1。
结论:假如从x开始的,F(2^k)=x

    2.当人数n不为 时,例如:3,5,7,9......

时,我们可以把人数变成,比如:9,我们可以-1把他变成8再算。截取一下上面博主的图

这时候,新的约瑟夫环的1号位置,是旧约瑟夫环的3号,就成了一个人数为的,首位置为3号的新约瑟夫环。
F(8) = F(2^3) = 1,也就是说这里的1代表的就是上一个问题中的3号。所以F(9) = 3。
同理可知,
假设n = + T,T可以随意取,比如1,2,3…….
假设n = 11,这时候n =  + 3,也就是说T=3,所以要先剔除3个元素。
我们在剔除了3个元素之后(分别是2,4,6),此时我们定格在2T+1(7)处,并且将2T+1(7)作为新的一号,而且这时候的约瑟夫环只剩下,也就是F( + 3) = 2*3 + 1 = 7,答案为7
总结一下这个规律:
F( +T) = 2T+1。

<2>当q≠2时,

        递归公式:F(n+1) = ( F(n) + q ) / (n+1)           F(n+1)= \frac{F(n)+q}{n+1}
0 1 2 3 4 5 ......  n-1       总共n人
设第q个人也就是下标为q-1的人被淘汰:
剩下n-1个人,如下:
分为两段(就是除去q-1这位兄台的剩余n-1人):
一段是:0  1  2   ......  q-2
另一段是:q q+1 q+2 ...... n-2  n-1 
这时,又来重复我们的老套路:将被淘汰的后一个人作为新的0号,于是新的如下:
0  1  2   ......     ..........     ........  n-2
从q开始,到最大数n-1,每个数都减去q,q之前的为负数,值为-1的就是最后一个数
emmm慢慢理解,我还是先记住递归公式吧。

粘贴一个队列题解:

#include <bits/stdc++.h>
#include <iostream>
#include <queue>
using namespace std;
int n,k,m;
int main(){
    cin>>n>>k>>m;
    queue<int> q;
    for(int i=k;i<n;i++)
        q.push(i);
    for(int i=0;i<k;i++)
        q.push(i);
    int cnt=0;
     while(q.size()>1){
         for(int i=1;i<m;i++){
             q.push(q.front());
             q.pop();
         }
           q.pop();
     }
    cout << q.front();
    return 0;
}
太妙了,先入先出,将k和后面的先输入队列,再将0到k-1的数输入队列,当队列中元素个数>1时进入循环,从1开始报数报到m-1,每报一个数就将队列的第一个数插到队列末尾,将第一个数删除。m-1后就是第m个数,将m踢出队列,继续。太妙了。



















全部评论

相关推荐

05-30 18:54
武汉商学院 Java
湫湫湫不会java:先投着吧,大概率找不到实习,没实习的时候再加个项目,然后把个人评价和荣誉奖项删了,赶紧成为八股战神吧,没实习没学历,秋招机会估计不多,把握机会。或者说秋招时间去冲实习,春招冲offer,但是压力会比较大
点赞 评论 收藏
分享
水墨不写bug:疑似没有上过大学
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务