约瑟夫环【第九届】【决赛】【C组】

         n 个人的编号是 1~n,如果他们依编号按顺时针排成一个圆圈,

        从编号是1的人开始顺时针报数。

     (报数是从1报起)当报到 k 的时候,这个人就退出游戏圈。下一个人重新从1开始报数。
  求最后剩下的人的编号。这就是著名的约瑟夫环问题。
  本题目就是已知 n,k 的情况下,求最后剩下的人的编号。
  题目的输入是一行,2个空格分开的整数n, k
  要求输出一个整数,表示最后剩下的人的编号。
  约定:0 < n,k < 1百万
  例如输入:
  10 3
  程序应该输出:
  4

通过一个示例模拟一下:

模拟过程解法:通过模拟报数的过程来逐渐淘汰参与者,直到只剩下一个人为止。

(这种方法在人数较少时是有效的,但对于大规模的输入可能效率不高。)

外圈是编号,内圈是人的代号,初始值是一样的,但当这个人出局后,出局的位置可以标记为0

容易知道,最后一定会剩下一个人。

模拟报数和淘汰过程:外层循环负责一直运行直到只剩下一个人为止。每轮循环中,通过内层循环模拟报数过程,直到报数达到 k。每次报数时,它都会检查当前的编号是否已经被淘汰(即a[j]是否为0)。如果没有被淘汰,就继续报数直到 cnt 达到 k。达到k时,当前报到k的人的编号(即a[j])会被设置为0,表示这个人被淘汰。

寻找最后剩下的人:在淘汰过程完成后,通过最后一个循环遍历a向量来找出最后剩下的人的编号,并输出这个编号。 

#include <bits/stdc++.h>
#define endl '\n'
using namespace std;

int main() {
	int n,k;
	cin>>n>>k;
	vector<int> a(n+1);//存储编号 
	for(int i=1;i<=n;i++){
		a[i]=i;
	}
	int cnt=0,j=0;
	for(int i=1;i<n;i++){//外层循环,每一局都会出去一个人 
		while(cnt<k){
			j++;
			if(j>n)  j=1;//如果超过人数,需要重新重1开始
			if(a[j]!=0) cnt++;
			
		}
		//cout<<a[j]<<" "; 
		a[j]=0;//出局后的处理 
		cnt=0;
	}
	for(int i=1;i<=n;i++){
		if(a[i]!=0) cout<<a[i];
	}
	return 0;
}

 递归解法:

使用约瑟夫问题的数学公式

 对于n个人,报数到k的情况,最后剩下的人的编号f(n, k)可以通过以下递推公式计算:

其中,f(n-1, k) 表示在前一次迭代中最后剩下的人的编号,初始条件是当只有一个人时,这个人的编号是0(根据公式的定义,实际编号应该加1)。我们可以通过这个公式直接计算最后剩下的人的编号。

这个递推公式的美妙之处在于,它将一个复杂的迭代问题转化为了一个可以通过简单递归解决的问题,极大地简化了问题的解法。通过递归地应用这个公式,我们可以高效地找到任意大小的约瑟夫环问题的解。 

对公式的解释如下:

  1. 基本情况:当圆圈中只剩下一个人时,即n=1的情况下,这个人就是最后的胜利者。在这种情况下,胜利者的编号是0(如果我们从0开始编号)。这就是递归的基础情况。

  2. 递归情况:假设我们知道对于n−1个人的情况下,最后的胜利者的编号为f(n−1,k),我们需要计算n个人的情况下最后的胜利者编号。

    • 在n个人中,第一个被淘汰的人的编号是k mod n(因为我们是从1开始数的,如果是从0开始,则是(k−1) mod n。
    • 当这个人被淘汰后,圈中就剩下了n−1个人,而下一个开始报数的人就取代了被淘汰人的位置,成为了新圈子中的第一个人。
    • 此时,问题就变成了一个新的约瑟夫环问题,但规模减小到了n−1个人。如果我们将淘汰的人之后的那个人看作新圈子的起点,那么我们可以假设新圈子是从0开始重新编号的。
  3. 编号转换:重要的一步是如何将新圈子的胜利者编号转换回原来的n个人中的编号。由于新圈子是从被淘汰的人的下一个人开始的,所以原圈子和新圈子的编号有一个固定的偏移量,即k mod n。因此,我们可以用f(n−1,k)+k来表示原圈子中胜利者的位置。但是,因为总人数是n,我们需要取模n来确保编号不会超出范围。

#include <iostream>
// 计算约瑟夫环问题的函数
int josephus(int n, int k) {
    if (n == 1)
        return 0;
    else
        // 递归调用,并应用约瑟夫环问题的解法公式
        return (josephus(n - 1, k) + k) % n;
}

int main() {
    int n, k;
    // 读取n和k的值
    std::cin >> n >> k;
    
    // 计算最后剩下的人的编号
    int result = josephus(n, k) + 1; // 加1是因为编号从1开始
    std::cout << result << std::endl;
    
    return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值