约瑟夫环

约瑟夫环

题目描述

此题来源于洛谷P1996

在这里插入图片描述


核心思路

在这里插入图片描述

问题:如何理解j=a[j]呢?

在这里插入图片描述

如何理解 a [ j ] = a [ a [ j ] ] a[j]=a[a[j]] a[j]=a[a[j]]呢?

在这里插入图片描述

出局图示如下:

在这里插入图片描述


代码

#include<iostream>
#include<algorithm>
using namespace std;
const int N=110;
int a[N];   //a[i]存放的是当前节点i的下一个节点的位置下标
int b[N];   //出局序列   b[i]这个值表示的是出局那个人的编号
int n,m;
int main()
{
    scanf("%d%d",&n,&m);
    //a[1]=2,即当前节点1号它的下一个节点的位置下标是2
    for(int i=1;i<n;i++)    //模拟链表
        a[i]=i+1;
    a[n]=1;     //第n个人指向第1人,形成一个环
    int k=1;    //记录每一个m间隔中此时已经数了多少个人
    int j=n;    //让j落到最后 这样当j=a[j]时就可以从1开始了
    for(int i=1;i<=n;i++)   //n个人都出局为止
    {
        //报数,当还没有报到m时,就继续
        while(k<m)
        {
            j=a[j];//j移动到下一个节点
            k++;    //报数个数+1
        }
        //退出循环后,说明此时正好报到m,因此此时编号为a[j]的这个人就要出局
        b[i]=a[j];
        //由于数到m的那个节点出局(即当前节点j的下一个节点a[j]出局了),此时让a[j]=a[a[j]]
        //那么此时当前节点j的下一个节点就当前节点j的下一个节点的下一个节点
        a[j]=a[a[j]];
        k=1;    //重置k为1
        //printf("%d出局 a[%d]=%d\n",b[i],j,a[j]);
    }
    //输出这n个人的出局序列
    for(int i=1;i<=n;i++)
        printf("%d ",b[i]);
    return 0;
}

题目描述

在这里插入图片描述


核心思路

我们可以给每一个人一个编号(索引值),每个人用字母来代替,下面举例 n = 8 , m = 3 n=8,m=3 n=8,m=3时,我们定义 f [ n ] [ m ] f[n][m] f[n][m]表示最后剩下的那个人的编号,因此我们只关系最后剩下来这个人的索引号的变化情况即可。

当某个人出队后,它的下一个人就成为了队首,而不再是原来的队首了。如下图,当C出队后,它的下一个人就是D,那么D就会成为队首,而不是让A成为队首。模拟发现,最终G是胜利者。

在这里插入图片描述

从8个人开始,每次杀掉一个人,去掉被杀的人,然后把杀掉那个人之后的第一个人作为开头重新编号

  • 第一次C被杀掉,人数变成7,D作为开头,(最终活下来的G的编号从6变成3)
  • 第二次F被杀掉,人数变成6,G作为开头,(最终活下来的G的编号从3变成0)
  • 第三次A被杀掉,人数变成5,B作为开头,(最终活下来的G的编号从0变成3)
  • 第四次E被杀掉,人数变成4,G作为开头,(最终活下来的G的编号从3变成0)
  • 第五次B被杀掉,人数变成3,D作为开头,(最终活下来的G的编号从0变成1)
  • 第六次H被杀掉,人数变成2,D作为开头,(最终活下来的G的编号从1变成1)
  • 第七次D被杀掉,人数变成1,G作为开头,(最终活下来的G的编号从1变成0)

由此可见,当只剩一个人时,这个人就是胜利者,他的编号肯定为0

问题:如何由最终的胜利者的编号反推呢?

现在我们知道了G的索引号的变化过程,那么我们反推一下,从 n = 7 n=7 n=7 n = 8 n=8 n=8的过程,怎样才能将 n = 7 n=7 n=7的排列顺序变为 n = 8 n=8 n=8时的排列顺序呢?我们【先把被杀掉的C补充回来,然后右移m个人,发现溢出了,再把溢出的补充在最前面】,经过这般操作后,就可以恢复到 n = 8 n=8 n=8时的排列顺序了。当删除某个节点时,这个节点后面的全部节点都向前平移 m m m个距离;当恢复某个节点时,那么全部节点都向后移动 m m m个单位,然后如果某些节点出界了,那么就然这些节点回到前面(设当前这轮有 x x x个人,那么如果越界的话,就让那些越界的人 % x \%x %x就可以回到前面了)。

因此,我们可以推导出递归关系式 f ( 8 , 3 ) = [ f ( 7 , 3 ) + 3 ] % 8 f(8,3)=[f(7,3)+3]\%8 f(8,3)=[f(7,3)+3]%8,推广到一般情况就是 f ( n , m ) = [ f ( n − 1 , m ) + m ] % n f(n,m)=[f(n-1,m)+m]\%n f(n,m)=[f(n1,m)+m]%n

在这里插入图片描述

递推公式

再把n=1这个最初的情况加上,就得到递推公式:

f ( n , m ) = { 0 n = 1 [ f ( n − 1 , m ) + m ] % n n > 1 f(n,m)=\begin {cases} 0 & n=1 \\ [f(n-1,m)+m]\%n & n>1 \end{cases} f(n,m)={0[f(n1,m)+m]%nn=1n>1

在这里插入图片描述

在这里插入图片描述


代码

class Solution {
public:
    int lastRemaining(int n, int m) {
        int pos = 0; // 最终活下来那个人的初始位置
        //i表示这一轮的人数  一直做到最初有n个人时 最终胜利者的位置
        for(int i = 2; i <= n; i++)
        {
            pos = (pos + m) % i;  // 每次循环右移
        }
        //这题的编号是从0开始,因此直接返回数组下标pos就是正确的编号
        //如果题目的编号是从1开始,但是由于我们的数组下标是从0开始,那么此时输出答案时就要数组下标pos+1,才能转成编号
        return pos;
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

卷心菜不卷Iris

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值