CSP-CCF★★★201712-2游戏★★★

目录

一、问题描述

二、解答

方法1:使用队列(非常巧妙,易理解)

方法2:使用一维数组

方法3:使用vector(90分,自己的思路+问AI)

方法4:仍然是vector(有一点糊里糊涂)

三、总结


一、问题描述

问题描述

  有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号小朋友获胜。

  给定nk,请问最后获胜的小朋友编号为多少?

输入格式

  输入一行,包括两个整数nk,意义如题目所述。

输出格式

  输出一行,包含一个整数,表示获胜的小朋友编号。

样例输入

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删除元素时,不只是找到该元素的所在位置就可以了,还应该想到如果先删除前面的元素,后面的元素所在位置就变了,但先删除后面元素,就不会影响前面的元素位置,所以不能立马删除,而是先记下位置,最后统一删除(从后往前删)。另外,把握好速度,控制好时间!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值