7-6 猴子选大王 (20 分)
一群猴子要选新猴王。新猴王的选择方法是:让N只候选猴子围成一圈,从某位置起顺序编号为1~N号。从第1号开始报数,每轮从1报到3,凡报到3的猴子即退出圈子,接着又从紧邻的下一只猴子开始同样的报数。如此不断循环,最后剩下的一只猴子就选为猴王。请问是原来第几号猴子当选猴王?
输入格式:
输入在一行中给一个正整数N(≤1000)。
输出格式:
在一行中输出当选猴王的编号。
输入样例:
11
输出样例:
7
常规解法:模拟
**思路分析:**题目本质上是猴子围圈,每报数三次淘汰一个猴子,仅剩一只猴子时退出所有循环。可以用一个数组表示猴子。数组下标作为猴子的编号,数组的值表示猴子的状态。用变量标记猴子编号时需注意:
1.假设n = 7,p = 5,则下一次标记应该为 6,0,1(猴子编号从0开始算,便于运用取余符号实现环形) 此处用p = (p+1)%n 来实现标记的移动,类似于环形队列。
2.已经淘汰的猴子不能再选,因此判定到该位置的猴子淘汰时本次报数(循环)不作数。
这几个环节比较容易错,可以中途输出指针的值和所淘汰的值方便debug。
代码如下:
#include <iostream>
#include <cstring>
using namespace std;
int main()
{
int a[1010], p = -1, n, num, i; //用p作为数组下标(猴子编号)
const int m = 3;
cin>>n;
num = n;
memset(a, -1, sizeof(a));
while(num > 0) //用num记录剩余的猴子
{
for(i = 1;i <= m;i++) //循环m次
{
p = (p+1)%n; //指针移动(类似于环形队列)
// cout<<p<<endl;
if(a[p] == 0) //该猴子已经淘汰,不算本次循环
i--;
}
a[p] = 0; //淘汰p编号的猴子
//cout<<"淘汰的是:"<<p<<endl;
num--;
}
cout<<p+1<<endl;
return 0;
}
以下是循环链表的代码:
#include <cstdio>
#include <algorithm>
using namespace std;
typedef struct node
{
int data;
struct node *next;
}Linklist;
void Create(Linklist *&L, int n) //创建循环链表
{
int i = 1, j;
Linklist *s, *r;
L = (Linklist *)malloc(sizeof(Linklist));
r = L;
for(j = 0;j < n;j++)
{
s = (Linklist *)malloc(sizeof(Linklist));
s->data = j+1;
r->next = s;
r = s;
}
r->next = L->next;
}
int Out(Linklist *&L,int n, int m, int k)
{
int count = 1;
Linklist *p = L->next;
while(p->data != k) //找到起点
p = p->next;
while(n > 1) //循环至只剩一人
{
int i;
for(i = 1;i < m;i++) p = p->next; //移动指针
p->data = p->next->data; //不用前驱结点的删除结点方法
p->next = p->next->next;
n--;
}
return p->data;
}
int main()
{
int n, m, k, ans;
Linklist *L;
printf("游戏人数:");
scanf("%d",&n);
printf("厄运数字数字:");
scanf("%d",&m);
printf("起点数:");
scanf("%d",&k);
Create(L, n);
ans = Out(L, n, m, k);
printf("最后能活下来的是:第%d位\n",ans);
return 0;
}
数学方法:递推
递推公式:f(1) = 0, f(n) = [f(n-1) + 3]%n
具体推导可看该文:https://blog.youkuaiyun.com/u011500062/article/details/72855826