目录
一、问题描述
问题描述
有n个小朋友围成一圈玩游戏,小朋友从1至n编号,2号小朋友坐在1号小朋友的顺时针方向,3号小朋友坐在2号小朋友的顺时针方向,……,1号小朋友坐在n号小朋友的顺时针方向。
游戏开始,从1号小朋友开始顺时针报数,接下来每个小朋友的报数是上一个小朋友报的数加1。若一个小朋友报的数为k的倍数或其末位数(即数的个位)为k,则该小朋友被淘汰出局,不再参加以后的报数。当游戏中只剩下一个小朋友时,该小朋友获胜。
例如,当n=5, k=2时:
1号小朋友报数1;
2号小朋友报数2淘汰;
3号小朋友报数3;
4号小朋友报数4淘汰;
5号小朋友报数5;
1号小朋友报数6淘汰;
3号小朋友报数7;
5号小朋友报数8淘汰;
3号小朋友获胜。
给定n和k,请问最后获胜的小朋友编号为多少?
输入格式
输入一行,包括两个整数n和k,意义如题目所述。
输出格式
输出一行,包含一个整数,表示获胜的小朋友编号。
样例输入
5 2
样例输出
3
样例输入
7 3
样例输出
4
数据规模和约定
对于所有评测用例,1 ≤ n ≤ 1000,1 ≤ k ≤ 9。
二、解答
方法1:使用队列(非常巧妙,易理解)
思路:确保每次报数时的小朋友在队首,如果报到淘汰数,用pop弹出队首元素,即淘汰这位小朋友;如果没有,将该位小朋友序号先放在队尾q.push(q.front()),再弹出。
参考博客:csp游戏201712-2_201712-2csp-优快云博客
代码:
#include<iostream>
#include<queue>
using namespace std;
int main()
{
int n;
cin >> n;
int k;
cin >> k;
queue<int>q;
for (int i = 1; i <= n; i++)
{
q.push(i);
}
int j = 1;
while (q.size() != 1)
{
if (j % k == 0 || j % 10 == k)
{
q.pop();//队首元素出列
}
else {
q.push(q.front());
q.pop();
}
j++;
}
cout << q.front();
return 0;
}
方法2:使用一维数组
思路:a[i]中的i表示小朋友的序号,保持不变,而a[i]的值一直在变,作为报数
参考博客:CSP-201712-2-游戏 90分原因分析 及 满分参考_csp90伤害-优快云博客
代码:
注意点:if语句中条件的顺序,顺序写反,可能都无法运行
#include<iostream>
using namespace std;
int main()
{
int n;
cin >> n;
int k;
cin >> k;
int a[1000] = { 0 };
for (int i = 1; i <= n; i++)
{
a[i] = i;//其位置就是序号,它的值在改变
}
int m = n;//当前所剩的小朋友
while (m != 1)
{
//特殊情况:n=1,k=1,只进行了一轮
for(int i=1;i<=n;i++)
{
if (a[i] == 0)//这个条件必须放在开头写,因为始终有0%k==0,然后就进入淘汰条件了!!!
{
continue;
}//a[i]==0,表示已被淘汰,跳过,进入下一次循环
else
if (a[i] % 10 == k || a[i] % k == 0)
{
a[i] =0;
m--;
//m==1就不能再执行了
if(m==1)
{
break;
}
}
else if (a[i] != 0) {
a[i] = a[i] + m;
}
}
}
for (int i = 1; i <= n; i++)
{
if (a[i] != 0)
{
cout << i;
}
}
return 0;
}
方法3:使用vector(90分,自己的思路+问AI)
思路:和方法2有一点类似,但是会运行超时,所以只能得90分。
类似于分组的方式,
涉及到vector如何同时删除若干个位置不连续的元素:位置靠后的先删除,即从后向前删除。
代码:
#include<iostream>
#include<vector>
using namespace std;
int main()
{
int n;
cin >> n;//n个小朋友
int k;
cin >> k;//报的数为k的倍数或其末位数(即数的个位)为k
vector<int>v;//记录小朋友序号的序列
for (int i = 1; i <= n; i++)
{
v.push_back(i);
}
int p = 1;
//int count = 0;
int i = n;//记录当前所剩小朋友的数量
vector<int> toDelete;//记录需要淘汰小朋友的序列
while(i!=1)
{
int num = 0;
toDelete.clear();
for (int q = p; q < p + i; q++)
{
if (q % k == 0 || q % 10 == k)
{
//v.erase(v.begin() + (q - p));
toDelete.push_back(q);
num++;
}
}
//从后往前删除,位置靠后的先删除。先记录,再删除。
// 此题本身就是有序的(从小到大),所以不需要排序,否则需要
for (int j = toDelete.size() - 1; j >= 0; --j)
{
v.erase(v.begin() + (toDelete[j] - p));
}
p = p + i;
i = i - num;
}
cout << v[0] << endl;
return 0;
}
方法4:仍然是vector(有一点糊里糊涂)
参考博客:【CSP试题回顾】201712-2-游戏(优化)_201712-2 游戏-优快云博客
思路:使用变量m表示当前的报数值,index表示当前应检查的小朋友索引。
代码:
#include<iostream>
#include<vector>
using namespace std;
int main()
{
int n;
cin >> n;//n个小朋友
int k;
cin >> k;
//报的数为k的倍数或其末位数(即数的个位)为k
vector<int>v;
for (int i = 0; i < n; i++)
{
v.push_back(i + 1);
}
int m = 1;
int index = 0;
while (v.size()!=1)
{
if (m % k == 0 || m % 10 == k)
{
v.erase(v.begin() + index);
n--;
index %= n;
}
else {
index = (index + 1) % n;//index为0-(n-1)
}
m++;
}
cout << v[0] << endl;
return 0;
}
三、总结
类似于约瑟夫环问题,但也仅仅是类似。报数问题是我的一道坎,上次的报数问题也花了我好长时间,这次是更长。学了STL的弊端:不知变通,一味地使用vector,以为它是万能的,但是有时候也不是。更多的问题依然是:考虑问题要全面(不然很容易陷入死胡同),就比如说这一题,我没有考虑到vector删除元素时,不只是找到该元素的所在位置就可以了,还应该想到如果先删除前面的元素,后面的元素所在位置就变了,但先删除后面元素,就不会影响前面的元素位置,所以不能立马删除,而是先记下位置,最后统一删除(从后往前删)。另外,把握好速度,控制好时间!!!