递推方法解决约瑟夫环问题
这是一道蓝桥杯2018年国赛C/C++组的问题,题目是这样的:
n 个人的编号是 1~n,如果他们依编号按顺时针排成一个圆圈,从编号是1的人开始顺时针报数。
(报数是从1报起)当报到 k 的时候,这个人就退出游戏圈。下一个人重新从1开始报数。
求最后剩下的人的编号。这就是著名的约瑟夫环问题。
本题目就是已知 n,k 的情况下,求最后剩下的人的编号。
题目的输入是一行,2个空格分开的整数n, k
要求输出一个整数,表示最后剩下的人的编号。
约定:0 < n,k < 1百万
例如输入:
10 3
程序应该输出:
4
普通解法
模拟整个过程:建一个bool数组,true表示此人还活着,false表示已经自杀。这种解法的数据结构理应采用循环链表,但是对于这道题,只需要采用一般的数组,稍加一点循环链表的思想,即在控制i的增加的时候,把一般的i++改为i=(i+1)%n即可。
代码如下:
//约瑟夫环问题
#include <stdio.h>
int main()
{
int n,k;
scanf("%d %d",&n,&k);
bool arr[n];
for(int i=0;i<n;i++){
arr[i]=true;
}
int now=0;
int false_num=0; //统计淘汰了几个人
int j;
if(n==1&&k==1){ //最简单的直接输出,下面就不用if了
printf("1");
return 0;
}
while(true){
int num = 0; //统计指针走过了几个true对象
for(j=now;;j=(j+1)%n){
if(arr[j]==true){
num++;
if(num==k){
arr[j]=false;
now = j+1;
false_num++;
num=0;
}
//printf("now = %d\nj = %d\nnum = %d\nfalse_num = %d\n",now,j,num,false_num);
}
if(false_num==n-1){
for(int i=0;i<n;i++){
if(arr[i]==true){
printf("%d",i+1);
return 0;
}
}
}
}
}
return 0;
}
这种方法时间复杂度为O(n*k)。
递推解法
F(1)=0
当有2个人的时候(N=2),报道(M-1)的人自杀,最后自杀的人是谁?应该是在只有一个人时,报数时得到的最后自杀的序号加上M,因为报到M-1的人已经自杀,只剩下2个人,另一个自杀者就是最后自杀者,用函数表示:
F(2)=F(1)+M
可以得到递推公式:
F(i)=F(i-1)+M
还是采用循环链表的思想
F(i)=(F(i-1)+M)%i
有了递推公式就可以在O(N)时间求出结果:
#include<stdio.h>
int main()
{
int n;
int k;
scanf("%d %d",&n,&k);
int result=0; //N=1情况
for (int i=2; i<=n; i++)
{
result=(result+k)%i;
}
printf("%d",result+1);
return 0;
}
2020国赛加油!