第十五届蓝桥杯C++ B组题解

 

A. 握手问题

填空题

        小蓝组织了一场算法交流会议,总共有 50 人参加了本次会议。在会议上, 大家进行了握手交流。按照惯例他们每个人都要与除自己以外的其他所有人进 行一次握手(且仅有一次)。但有 7 个人,这 7 人彼此之间没有进行握手(但 这 7 人与除这 7 人以外的所有人进行了握手)。请问这些人之间一共进行了多 少次握手?

        注意 A 和 B 握手的同时也意味着 B 和 A 握手了,所以算作是一次握手。

思路:

模拟

        将50个人从1到50标号,对于每两个人之间只握一次手,我们可以将问题转化为每个人只主动和标号比他大的人握一次手,那么标号为 1 的人,需要主动握 49 次,标号为 2 的人,需要主动握 48 次,以此类推,标号为 i 的人需要主动握 50 - i 次。(这边强烈建议 I 人往后排)

        经过上述流程,我们即可得到不加限制条件下的总握手次数为 1 + 2 + ... + 48 + 49 = 1225

        接下来我们来处理限制条件,我们假设相互之间没有握手的这七个人为标号 1 ~ 7 的七个人,那么如果他们之间有相互握手的话,根据前述流程可得他们之间握手次数为 1 + 2 + ... + 6  = 21

        用总次数减去多余握手次数(前七个人间相互握手次数)即得到限制条件下的握手次数,即为 1204

可用代码实现求和、求差过程

       

代码实现: 

#include <iostream>

using namespace std;

int main()
{
	int sum1 = 0, sum2 = 0;
	for(int i = 1; i <= 50; i ++)
	{
		sum1 += 50 - i;
	}
	
	for(int i = 1; i <= 7; i ++)
	{
		sum2 += 7 - i;
	}
	
	printf("%d", sum1 - sum2);
	return 0;
} 

 

B. 小球反弹

填空题

        有一长方形,长为 343720 单位长度,宽为 233333 单位长度。在其内部左 上角顶点有一小球(无视其体积),其初速度如图所示且保持运动速率不变,分 解到长宽两个方向上的速率之比为 dx : dy = 15 : 17。小球碰到长方形的边框时 会发生反弹,每次反弹的入射角与反射角相等,因此小球会改变方向且保持速 率不变(如果小球刚好射向角落,则按入射方向原路返回)。从小球出发到其第 一次回到左上角顶点这段时间里,小球运动的路程为多少单位长度?答案四舍 五入保留两位小数。

思路:

不会写,不知道答案是多少,不知道在哪找答案

代码实现:

//整个空框占个位置(实际没打算补)

C. 好数

编程题

         一个整数如果按从低位到高位的顺序,奇数位(个位、百位、万位 · · · )上 的数字是奇数,偶数位(十位、千位、十万位 · · · )上的数字是偶数,我们就称 之为“好数”。

        给定一个正整数 N,请计算从 1 到 N 一共有多少个好数。

 输入格式

 一个整数 N(1 ≤ N ≤ 1e7)

输出格式 

输出一个整数表示 1 到 N 之间好数的个数

样例输入1 

24

样例输出1 

7

样例输入2

2024

样例输出2 

150

 思路:

枚举 + 数位拆分

         对于 1 ~ N 中的每一个数字,我们都需要判断它是否是好数(依次判断每一位数字是否符合条件),于是我们需要对每一位数字进行数位拆分,因为 N 最大为 1e7,所以每个数字数位拆分的最大时间复杂度 lgN 小于10。依次枚举1 ~ N中每一个数字,对其进行数位拆分判断是否为好数,计数输出答案即可

代码实现:

#include <iostream>

using namespace std;

bool check(int x)
{
	int k = 1, w;
	while(x)
	{
		w = x % 10;
		if(k % 2 != w % 2) return false;
		
		x /= 10;
		k ++;
	}
	return true;
}

int main()
{
	int n, ans = 0;
	scanf("%d",&n);
	
	for(int i = 1; i <= n; i ++)
	{
		if(check(i))
		{
			ans ++;
		}
	}
	
	printf("%d", ans);
	return 0;
}

 

D. R 格式 

编程题

        小蓝最近在研究一种浮点数的表示方法:R 格式。对于一个大于 0 的浮点数 d,可以用 R 格式的整数来表示。给定一个转换参数 n,将浮点数转换为 R 格式整数的做法是:

        1. 将浮点数乘以 2^n(2 的 n 次方) ;

        2. 四舍五入到最接近的整数。

输入格式 

一行输入一个整数 n 和一个浮点数 d,分别表示转换参数,和待转换的浮点数。

输出格式 

输出一行表示答案:d 用 R 格式表示出来的值。

 样例输入

2 3.14

样例输出 

13

思路:

高精度加法模拟

        用 string 读入浮点数 d,将其逆置之后,循环 n 次,每次循环使 d 加上它本身,最后四舍五入输出整数位即可(最初逆置过了,记得从后往前输出)

代码实现:

#include <iostream>
#include <string>
#include <algorithm>

using namespace std;

int n;
string s;

// s 自加 s 
void Do()
{
	int a, f = 0, m = s.size();
	for(int i = 0; i < m; i ++)
	{
		if(s[i] == '.') continue;
		a = f; f = 0;
		a += 2 * (s[i] - '0');
		
		if(a >= 10) f = 1;
		a %= 10;
		s[i] = '0' + a;
	}
	
	if(f) s += '1';
} 

int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	
	cin >> n >> s;
	reverse(s.begin(), s.end());
	
	//加倍 n 次 
	while(n --)
	{
		Do();
	}
	
	//四舍五入
	int a, m = 0, f = 0;
	while(s[m] != '.') m ++;
	
	if(s[m - 1] >= '5') f = 1; m ++;
	while(f && m < s.size())
	{
		s[m] += 1;
		f = 0;
		if(s[m] > '9')
		{
			s[m] = '0';
			f = 1;
		}
		
		m ++;
	}
	
	if(f) s += '1';
	
	//输出
	n = s.size() - 1;
	while(s[n] != '.')
	{
		cout << s[n --];
	}
	return 0;
}

 

E.宝石组合

编程题

        在一个神秘的森林里,住着一个小精灵名叫小蓝。有一天,他偶然发现了一个隐藏在树洞里的宝藏,里面装满了闪烁着美丽光芒的宝石。这些宝石都有 着不同的颜色和形状,但最引人注目的是它们各自独特的 “闪亮度” 属性。每颗 宝石都有一个与生俱来的特殊能力,可以发出不同强度的闪光。小蓝共找到了 N 枚宝石,第 i 枚宝石的 “闪亮度” 属性值为 Hi,小蓝将会从这 N 枚宝石中选出三枚进行组合,组合之后的精美程度 S 可以用下图公式来衡量

        小蓝想要使得三枚宝石组合后的精美程度 S 尽可能的高,请你帮他找出精美程度最高的方案。如果存在多个方案 S 值相同,优先选择按照 H 值升序排列后字典序最小的方案。

 输入格式:

 第一行包含一个整数 N 表示宝石个数。

第二行包含 N 个整数表示 N 个宝石的 “闪亮度”。

输出格式: 

输出一行包含三个整数表示满足条件的三枚宝石的 “闪亮度”。

 样例输入:

5

1 2 3 4 9

样例输出: 

1 2 3

 思路:

约数 + 枚举

        打表可以发现精美程度 S 其实是三个宝石闪亮度的最大公约数(别问我怎么发现的,我也不知道,群友说的,我赛时纯暴力),当然,佬们也可以尝试用题给式子推导一下,本题解不再赘述(主要我也不会推导)

        我们用邻接表来存所有宝石闪亮度中,1 ~ 100000的倍数,st数组用于存放所有宝石闪亮度中1 ~ 100000之间每个数字的倍数出现的次数。从大到小去寻找第一个倍数出现3个或3个以上的数字,则这个数字就是所有宝石选择方案中能得到的最大的精美程度 S。

        此处注意我们将所有宝石闪亮度从大到小排序,优先去在邻接表中插入较大闪亮度(用头插法),以保证我们后面输出时按字典序(从小到大的顺序,我也好奇为什么蓝桥杯要整个字典序这个名词)去输出。

        注意:数组一定要开得足够大,因为邻接表中每个宝石的闪亮度会被存它的约数个数次,只开1e5是远远不够的

代码实现: 

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 1e6 + 20;
int n, q[N], st[N];
int h[N], e[2 * N], ne[2 * N], idx;

bool cmp(int a, int b)
{
	return a > b;
}

void add(int x, int y)
{
	e[idx] = y;
	ne[idx] = h[x];
	h[x] = idx ++;
}

void check(int x)
{
	for(int i = 1; i <= x / i; i ++)
	{
		if(x % i == 0)
		{
			add(i, x);
			
			st[i] ++;
			if(i != x / i)
			{
				st[x / i] ++;
				add(x / i, x);
			}
		}
	}
}

int main()
{
	scanf("%d",&n);
	for(int i = 1; i <= n; i ++)
	{
		scanf("%d",&q[i]);
	}
	
	sort(q + 1, q + n + 1, cmp);
	
	memset(h, -1, sizeof(h));
	for(int i = 1; i <= n; i ++)
	{
		check(q[i]);
	}
	
	int k = 1;
	for(int i = 100010; i > 1; i --)
	{
		if(st[i] >= 3)
		{
			k = i;
			break;
		}
	}
	
	for(int i = h[k], cnt = 0; cnt < 3; i = ne[i], cnt ++)
	{
		printf("%d ", e[i]);
	}
	
	return 0;
}

 

F.数字接龙

编程题

        小蓝最近迷上了一款名为《数字接龙》的迷宫游戏,游戏在一个大小为 N × N 的格子棋盘上展开,其中每一个格子处都有着一个 0 . . . K − 1 之间的整 数。游戏规则如下:

        1. 从左上角 (0, 0) 处出发,目标是到达右下角 (N − 1, N − 1) 处的格子,每一 步可以选择沿着水平/垂直/对角线方向移动到下一个格子。

        2. 对于路径经过的棋盘格子,按照经过的格子顺序,上面的数字组成的序列 要满足:0, 1, 2, . . . , K − 1, 0, 1, 2, . . . , K − 1, 0, 1, 2 . . . 。

        3. 途中需要对棋盘上的每个格子恰好都经过一次(仅一次)。

        4. 路径中不可以出现交叉的线路。例如之前有从 (0, 0) 移动到 (1, 1),那么再 从 (1, 0) 移动到 (0, 1) 线路就会交叉。

        为了方便表示,我们对可以行进的所有八个方向进行了数字编号,如下图 2 所示;因此行进路径可以用一个包含 0 . . . 7 之间的数字字符串表示,如下图 1 是一个迷宫示例,它所对应的答案就是:41255214。

        现在请你帮小蓝规划出一条行进路径并将其输出。如果有多条路径,输出 字典序最小的那一个;如果不存在任何一条路径,则输出 −1。

 输入格式:

第一行包含两个整数 N、K。

接下来输入 N 行,每行 N 个整数表示棋盘格子上的数字。

输出格式: 

输出一行表示答案。如果存在答案输出路径,否则输出 −1。

样例输入: 

3 3

0 2 0

1 1 1

2 0 2

样例输出:

41255214

 思路:

DFS模拟

        我们从起点出发,以0 ~ 7 的顺序依次判断当前点的周围8个点是否符合题给要求(该点上的数字、路径是否交叉、该点是否被走过、是否越界),当搜到第一条路径时,直接全部返回(因为我们是按 0 ~ 7 的顺序去搜的,所以第一次找到的路径一定为字典序最小的路径)。这里采用 unordered_map去记录两点之间是否存在路径,进而判断路径是否交叉。

        注意:

                1. 从main函数进入dfs的时候,u的传值一定不要直接传1,因为k可能为1(血的教训)

                2.题面有歧义,当测试点为 1 1 0 (0为棋盘上的数字)时,要输出-1还是无输出不确定,赛时我写的没有输出,但是C语言网上按-1输出的,卡了好久才发现

代码实现: 

#include <iostream>
#include <vector>
#include <unordered_map>

using namespace std;

bool f;
int n, k, q[20][20], st[20][20];
vector<int> ans, w;
unordered_map<long long, int> mp; //用于记录两点之间是否有连线

void dfs(int x, int y, int cnt, int u)
{	
	if(f) return;
	if(cnt == n * n)
	{
		if(x == n && y == n)
		{
			f = 1;
			
			if(ans > w) ans = w;
			return;
		}
		return;
	}
	
	if(x == n && y == n)
	{
		return;
	}
	
	int a, b;
	for(int i = 0; i < 8; i ++)
	{
		switch(i)
		{
			case 0: a = x - 1; b = y; break;
			case 1: a = x - 1; b = y + 1; break;
			case 2:	a = x; b = y + 1; break;
			case 3: a = x + 1; b = y + 1; break;
			case 4: a = x + 1; b = y; break;
			case 5: a = x + 1; b = y - 1; break;
			case 6: a = x; b = y - 1; break;
			case 7: a = x - 1; b = y - 1; break;
		}
		
		if(i == 1)
		{
			if(mp[((x - 1) * 1000 + y) * 10000000 + x * 1000 + (y + 1)])
			{
				continue;
			}
		}
		else if(i == 3)
		{
			if(mp[((x + 1) * 1000 + y) * 10000000 + x * 1000 + (y + 1)])
			{
				continue;
			}
		}
		else if(i == 5)
		{
			if(mp[((x + 1) * 1000 + y) * 10000000 + x * 1000 + (y - 1)])
			{
				continue;
			}
		}
		else if(i == 7)
		{
			if(mp[((x - 1) * 1000 + y) * 10000000 + x * 1000 + (y - 1)])
			{
				continue;
			}
		}
		
		if(a > 0 && a <= n && b > 0 && b <= n && q[a][b] == u && !st[a][b])
		{
			if(i % 2)	
			{
				mp[(a * 1000 + b) * 10000000 + x * 1000 + y] = 1;
				mp[(x * 1000 + y) * 10000000 + a * 1000 + b] = 1;
			}
			st[a][b] = 1;
			
			w[cnt] = i;
			
			dfs(a, b, cnt + 1, (u + 1) % k);
			
			st[a][b] = 0;
			if(i % 2)
			{
				mp[(a * 1000 + b) * 10000000 + x * 1000 + y] = 0;
				mp[(x * 1000 + y) * 10000000 + a * 1000 + b] = 0;
			}
			
			if(f) return;
		}
	}
}

int main()
{
	scanf("%d %d",&n, &k);
	for(int i = 1; i <= n; i ++)
	{
		for(int j = 1; j <= n; j ++)
		{
			scanf("%d",&q[i][j]);
			ans.push_back(10);
			w.push_back(0);
		}
	}
	
	if(n == 1 || q[1][1] != 0)
	{
		printf("-1");
		return 0;
	}
	
	st[1][1] = 1;
	dfs(1, 1, 1, 1 % k);

	if(f)
	{
		for(int i = 1; i < n * n; i ++)
		{
			printf("%d", ans[i]);
		}
	}
	else printf("-1");
	return 0;
}

G.爬山

编程题

        小明这天在参加公司团建,团建项目是爬山。在 x 轴上从左到右一共有 n座山,第 i 座山的高度为 hi。他们需要从左到右依次爬过所有的山,需要花费的体力值为 S = Σni=1hi。

        然而小明偷偷学了魔法,可以降低一些山的高度。他掌握两种魔法,第一种魔法可以将高度为 H 的山的高度变为 ⌊√H⌋,可以使用 P 次;第二种魔法可以将高度为 H 的山的高度变为 ⌊H/2⌋,可以使用 Q 次。并且对于每座山可以按任意顺序多次释放这两种魔法。

        小明想合理规划在哪些山使用魔法,使得爬山花费的体力值最少。请问最优情况下需要花费的体力值是多少?

 输入格式:

输入共两行。

第一行为三个整数 n,P,Q。

第二行为 n 个整数 h1,h2,. . . ,hn。

输出格式: 

输出共一行,一个整数代表答案。

样例输入:

4 1 1

4 5 6 49

样例输出:

18

思路: 

贪心

        先声明一下:贪心做法是我在赛时的写法,同时也是蓝桥官方直播时给出的做法,但在4月13日 13:08被群友以 2 1 1 48 49的测试用例hack了,所以本题解提供的属于是错误思路(博主太菜了,想不出正解,见谅),诸君图一乐即可,若有所思所启,荣幸之至,绝无误人子弟之意。

        较片面地看来,我们只需每次对当前最高的山使用魔法使其变矮,使用完所有魔法得到的便是最优结果,而在使用魔法时,要优先使用能使当前最高山变得更矮的魔法,故使用大根堆来存储所有山的高度,每次弹出堆顶,对其进行操作(若有两种魔法,则二选其优,否则有哪个用哪个),最后将所有的山高度求和即可,时间复杂度O(nlogn)。

 代码实现:

#include <iostream>
#include <queue>
#include <cmath>

using namespace std;

int n, x, y;
priority_queue<int> q;

long long a, b, ans;

int main()
{
	scanf("%d %d %d",&n, &x, &y);
	for(int i = 1; i <= n; i ++)
	{
		scanf("%lld",&a);
		q.push(a);
	}
	
	while(x || y)
	{
		long long t = q.top();
		q.pop();
		a = t / 2, b = sqrt(t);
		
		if(x && y)
		{
			if(a < b)
			{
				y --;
				q.push(a);
			}
			else
			{
				x --;
				q.push(b);
			}
			
		}
		else if(x)
		{
			x --;
			q.push(b);
		}
		else
		{
			y --;
			q.push(a);
		}
	}
	
	while(!q.empty())
	{
		ans += q.top();
		q.pop();
	}
	
	printf("%lld", ans);
}

 

H.拔河

编程题

        小明是学校里的一名老师,他带的班级共有 n 名同学,第 i 名同学力量值为 ai。在闲暇之余,小明决定在班级里组织一场拔河比赛。

        为了保证比赛的双方实力尽可能相近,需要在这 n 名同学中挑选出两个队伍,队伍内的同学编号连续:{ al1, al1+1, ..., ar1−1, ar1 } 和 { al2, al2+1, ..., ar2−1, ar2 },其中 l1 ≤ r1 < l2 ≤ r2。

        两个队伍的人数不必相同,但是需要让队伍内的同学们的力量值之和尽可能相近。请计算出力量值之和差距最小的挑选队伍的方式。

输入格式: 

输入共两行。

第一行为一个正整数 n。

第二行为 n 个正整数 ai。

输出格式: 

输出共一行,一个非负整数,表示两个队伍力量值之和的最小差距。

样例输入:

5
10 9 8 12 14

 样例输出: 

 思路:

枚举 + 二分 感谢学长 @福建易烊万玺 的顶级理解思路,让本蒟蒻在有生之年还能将这道题写出来

        因为两支队伍均要求连续,我们可以将用前缀和预处理以缩短求连续区间和的时间。事先将所有可能的区间存入multiset容器中,枚举 l1 和 r1,在每一个 r1 被枚举到时,将multiset容器中所有以当前 r1 为左端点的区间全部删掉,则容器中剩余的区间即为所有可能的第二队的区间,随后枚举 l1 ,用multiset内置的二分函数lower_bound去找到第一个大于等于 当前枚举区间力量值的值,令其与当前枚举区间力量值相减,得到的结果与当前答案比较取较小值,再取容器中最大的不大于前枚举区间力量值的值,相减后与当前答案取最小值,枚举结束即可得到最小可能差值。(由于本人较菜,描述可能有些难以消化,建议结合代码理解,若还是理解不了,请移步参考文章

 代码实现:

#include <iostream>
#include <set>
#include <algorithm>

using namespace std;

const int N = 1010;
int n;
long long ans = 1e9, q[N];
multiset<long long> s; 

int main()
{
	scanf("%d",&n);
	for(int i = 1; i <= n; i ++)
	{
		scanf("%lld",&q[i]);
		q[i] += q[i - 1];
	}
	
	for(int i = 1; i <= n; i ++)
	{
		for(int j = i; j <= n; j ++)
		{
			s.insert(q[j] - q[i - 1]);
		}
	}
	
	int res;
	for(int r1 = 1; r1 <= n; r1 ++)
	{
		for(int r2 = r1; r2 <= n; r2 ++)
		{
			auto k = s.find(q[r2] - q[r1 - 1]);
			s.erase(k);
		}
		
		for(int l1 = 1; l1 <= r1; l1 ++)
		{
			auto k = s.lower_bound(q[r1] - q[l1 - 1]);
			if(k != s.end())
			{
				ans = min(ans, abs(*k - q[r1] + q[l1 - 1]));
			}
			if(k != s.begin())
			{
				k --;
				ans = min(ans, abs(*k - q[r1] + q[l1 - 1]));
			}
		}
	}
	
	printf("%lld", ans);
	return 0;
}

 

 

 

关于第十五届蓝桥杯C++的题目解析,目前尚未有官方完整的公开文档或博客文章提供详细的解答。然而,可以基于以往的经验和趋势推测可能涉及的内容以及解题思路。 以下是针对蓝桥杯常见类型的分析与应对策略: ### 填空题部分 通常情况下,填空题会考察基础算法知识或者简单的逻辑推理能力。例如,在往年的比中曾出现过计算日期差、字符串匹配等问题[^1]。对于这类问题,建议熟悉基本的数据结构操作方法及其时间复杂度评估技巧。 ```cpp #include <iostream> using namespace std; int main() { int year, month, day; cin >> year >> month >> day; // 输入年月日 // 计算某一天是一年中的第几天 static const int daysInMonth[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; bool isLeapYear = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); if(isLeapYear){ daysInMonth[1]++; } int sumDays=0; for(int i=0;i<month-1;i++) { sumDays +=daysInMonth[i]; } cout <<sumDays+day<< endl; } ``` 上述代码片段展示了如何判断给定的一天是该年度内的具体哪一天,并考虑到了闰年的特殊情况处理方式。 ### 编程大题部分 编程大题往往涵盖了更广泛的计算机科学领域知识点,比如动态规划、贪心算法、图论等高级主题。下面列举几个典型例子并附带简单说明: #### 动态规划类题目 此类题目一般要求选手具备较强的抽象建模能力和递推关系构建经验。通过定义状态转移方程式来解决问题是最常用的手法之一。 #### 贪婪算法应用实例 当面对资源分配最优解求取场景时,贪婪法则显得尤为重要。它总是做出局部看来最佳的选择希望达到全局优化目标。 #### 图遍历相关练习 涉及到网络拓扑排序或者是最短路径寻找等方面的应用广泛存在于历年真题当中。掌握BFS(广度优先搜索)/DFS(深度优先搜索),Dijkstra,DAG等相关理论有助于快速解决这些问题。 尽管当前无法确切得知第十五届的具体考情安排,但从历史数据来看,保持扎实的基础功底加上灵活运用各类经典模型将是成功的关键所在。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值