2022年团体程序设计天梯赛C++个人题解附带解题思路

L1-1 今天我要赢 (5 分)

2018 年我们曾经出过一题,是输出“2018 我们要赢”。今年是 2022 年,你要输出的句子变成了“我要赢!就在今天!”然后以比赛当天的日期落款。

输入格式:

本题没有输入。

输出格式:

输出分 2 行。在第一行中输出 I’m gonna win! Today!,在第二行中用 年年年年-月月-日日 的格式输出比赛当天的日期。已知比赛的前一天是 2022-04-22

输入样例:

输出样例

(第二行的内容要你自己想一想,这里不给出):

I’m gonna win! Today!
这一行的内容我不告诉你…… 你要自己输出正确的日期呀~

代码

#include<iostream>
#define endl '\n'
#define ios ios::sync_with_stdio(false)
#define tie cin.tie(nullptr),cout.tie(nullptr)
using namespace std;
void solve() {
	cout << "I'm gonna win! Today!" << endl;
	cout << "2022-04-23" << endl;
}
int main() {
	ios, tie;
	solve();
	return 0;
}

L1-2 种钻石 (5 分)

在这里插入图片描述

2019年10月29日,中央电视台专题报道,中国科学院在培育钻石领域,取得科技突破。科学家们用金刚石的籽晶片作为种子,利用甲烷气体在能量作用下形成碳的等离子体,慢慢地沉积到钻石种子上,一周“种”出了一颗 1 克拉大小的钻石。
本题给出钻石的需求量和人工培育钻石的速度,请你计算出货需要的时间。

输入格式:

输入在一行中给出钻石的需求量 N(不超过 107 的正整数,以微克拉为单位)和人工培育钻石的速度 v(1≤v≤200,以微克拉/天为单位的整数)。

输出格式:

在一行中输出培育 N 微克拉钻石需要的整数天数。不到一天的时间不算在内。

输入样例:

102000 130

输出样例:

784

代码

#include<iostream>
#define endl '\n'
#define ios ios::sync_with_stdio(false)
#define tie cin.tie(nullptr),cout.tie(nullptr)
using namespace std;
void solve() {
	int n, v; cin >> n >> v;
	cout << n / v << endl;
}
int main() {
	ios, tie;
	solve();
	return 0;
}

L1-3 谁能进图书馆 (10 分)

为了保障安静的阅读环境,有些公共图书馆对儿童入馆做出了限制。例如“12 岁以下儿童禁止入馆,除非有 18 岁以上(包括 18 岁)的成人陪同”。现在有两位小/大朋友跑来问你,他们能不能进去?请你写个程序自动给他们一个回复。

输入格式:

输入在一行中给出 4 个整数:
禁入年龄线 陪同年龄线 询问者1的年龄 询问者2的年龄
这里的禁入年龄线是指严格小于该年龄的儿童禁止入馆;陪同年龄线是指大于等于该年龄的人士可以陪同儿童入馆。默认两个询问者的编号依次分别为 12;年龄和年龄线都是 [1, 200] 区间内的整数,并且保证 陪同年龄线 严格大于 禁入年龄线

输出格式:

在一行中输出对两位询问者的回答,如果可以进就输出 年龄-Y,否则输出 年龄-N,中间空 1 格,行首尾不得有多余空格。
在第二行根据两个询问者的情况输出一句话:

  1. 如果两个人必须一起进,则输出 qing X zhao gu hao Y,其中 X 是陪同人的编号, Y 是小孩子的编号;

  2. 如果两个人都可以进但不是必须一起的,则输出 huan ying ru guan

  3. 如果两个人都进不去,则输出 zhang da zai lai ba

  4. 如果一个人能进一个不能,则输出 X: huan ying ru guan,其中 X 是可以入馆的那个人的编号。

输入样例 1:

12 18 18 8

输出样例 1:

18-Y 8-Y
qing 1 zhao gu hao 2

输入样例 2:

12 18 10 15

输出样例 2:

10-N 15-Y
2: huan ying ru guan

代码

#include<iostream>
#define endl '\n'
#define ios ios::sync_with_stdio(false)
#define tie cin.tie(nullptr),cout.tie(nullptr)
using namespace std;
void solve() {
	int bage, wage; cin >> bage >> wage;
	int a, b; cin >> a >> b;
	if (a > b) {
		if (b >= bage) {
			cout << a << "-Y " << b << "-Y" << endl;
			cout << "huan ying ru guan" << endl;
		}
		else if (b < bage && a >= wage) {
			cout << a << "-Y " << b << "-Y" << endl;
			cout << "qing 1 zhao gu hao 2" << endl;
		}
		else if (a < bage) {
			cout << a << "-N " << b << "-N" << endl;
			cout << "zhang da zai lai ba" << endl;
		}
		else if (b < bage && a < wage) {
			cout << a << "-Y " << b << "-N" << endl;
			cout << "1: huan ying ru guan" << endl;
 		}
	}
	else {
		if (a >= bage) {
			cout << a << "-Y " << b << "-Y" << endl;
			cout << "huan ying ru guan" << endl;
		}
		else if (a < bage && b >= wage) {
			cout << a << "-Y " << b << "-Y" << endl;
			cout << "qing 2 zhao gu hao 1" << endl;
		}
		else if (b < bage) {
			cout << a << "-N " << b << "-N" << endl;
			cout << "zhang da zai lai ba" << endl;
		}
		else if (a < bage && b < wage) {
			cout << a << "-N " << b << "-Y" << endl;
			cout << "2: huan ying ru guan" << endl;
		}
	}
}
int main() {
	ios, tie;
	solve();
	return 0;
}

L1-4 拯救外星人 (10 分)

在这里插入图片描述

你的外星人朋友不认得地球上的加减乘除符号,但是会算阶乘 —— 正整数 N 的阶乘记为 “N!”,是从 1 到 N 的连乘积。所以当他不知道“5+7”等于多少时,如果你告诉他等于“12!”,他就写了“479001600” 这个答案。
本题就请你写程序模仿外星人的行为。

输入格式:

输入在一行中给出两个正整数 A 和 B。

输出格式:

在一行中输出 (A+B) 的阶乘。题目保证 (A+B) 的值小于 12。

输入样例:

3 6

输出样例:

362880

代码

#include<iostream>
#define endl '\n'
#define ios ios::sync_with_stdio(false)
#define tie cin.tie(nullptr),cout.tie(nullptr)
using namespace std;
void solve() {
	int a, b; cin >> a >> b;
	long long c = 1;
	for (int i = 1; i <= (a + b); i++) {
		c *= i;
	}
	cout << c << endl;
}
int main() {
	ios, tie;
	solve();
	return 0;
}

L1-5 试试手气 (15 分)

在这里插入图片描述

我们知道一个骰子有 6 个面,分别刻了 1 到 6 个点。下面给你 6 个骰子的初始状态,即它们朝上一面的点数,让你一把抓起摇出另一套结果。假设你摇骰子的手段特别精妙,每次摇出的结果都满足以下两个条件:

  • 1、每个骰子摇出的点数都跟它之前任何一次出现的点数不同;
  • 2、在满足条件 1 的前提下,每次都能让每个骰子得到可能得到的最大点数。

那么你应该可以预知自己第 n 次(1≤n≤5)摇出的结果。

输入格式:

输入第一行给出 6 个骰子的初始点数,即 [1,6] 之间的整数,数字间以空格分隔;第二行给出摇的次数 n(1≤n≤5)。

输出格式:

在一行中顺序列出第 n 次摇出的每个骰子的点数。数字间必须以 1 个空格分隔,行首位不得有多余空格。

输入样例:

3 6 5 4 1 4
3

输出样例:

4 3 3 3 4 3

样例解释:

这 3 次摇出的结果依次为:
6 5 6 6 6 6
5 4 4 5 5 5
4 3 3 3 4 3

思路

对于每个骰子,设置一个访问数组,初始数字标记为true,因为每次都摇最大,所以从6开始往前走 n 次,遇到true跳过,否则n减一,n为0时,此时的j则为我们最终摇到的骰子数目

代码

#include<iostream>
#define endl '\n'
#define ios ios::sync_with_stdio(false)
#define tie cin.tie(nullptr),cout.tie(nullptr)
using namespace std;
const int maxn = 10;
int arr[maxn];
int vis[6][maxn];
void solve() {
	for (int i = 0; i < 6; i++) {
		cin >> arr[i];
		vis[i][arr[i]] = true;
	}
	int k; cin >> k;
	for (int i = 0; i < 6; i++) {
		int temp = k, j = 6;
		for (j; j > 0 && temp; j--) {
			if (vis[i][j])continue;
			else {
				vis[i][j] = true;
				temp--;
			}
		}
		cout << j + 1;
		if (i != 5)cout << ' ';
	}
}
int main() {
	ios, tie;
	solve();
	return 0;
}

L1-6 斯德哥尔摩火车上的题 (15 分)

在这里插入图片描述

上图是新浪微博上的一则趣闻,是瑞典斯德哥尔摩火车上的一道题,看上去是段伪代码:

s = ‘’
a = ‘1112031584’
for (i = 1; i < length(a); i++) {
	if (a[i] % 2 == a[i-1] % 2) {
	s += max(a[i], a[i-1])
	}
}
goto_url(‘www.multisoft.se/’ + s)

其中字符串的 + 操作是连接两个字符串的意思。所以这道题其实是让大家访问网站 www.multisoft.se/112358(注意:比赛中千万不要访问这个网址!!!)。
当然,能通过上述算法得到 112358 的原始字符串 a 是不唯一的。本题就请你判断,两个给定的原始字符串,能否通过上述算法得到相同的输出?

输入格式:

输入为两行仅由数字组成的非空字符串,长度均不超过 104,以回车结束。

输出格式:

对两个字符串分别采用上述斯德哥尔摩火车上的算法进行处理。如果两个结果是一样的,则在一行中输出那个结果;否则分别输出各自对应的处理结果,每个占一行。题目保证输出结果不为空。

输入样例 1:

1112031584
011102315849

输出样例 1:

112358

输入样例 2:

111203158412334
12341112031584

输出样例 2:

1123583
112358

思路

将题中给的代码copy下来就行,分别对两次的字符串跑一遍,每次结果一致则输出第一次,不同则都输出。

代码

#include<iostream>
#include<string>
#define endl '\n'
#define ios ios::sync_with_stdio(false)
#define tie cin.tie(nullptr),cout.tie(nullptr)
using namespace std;
const int maxn = 10;
void solve() {
	string a, b;
	cin >> a >> b;
	string c = "";
	int lena = a.size(), lenb = b.size();
	for (int i = 1; i < lena; i++) {
		if (a[i] % 2 == a[i - 1] % 2) {
			c += max(a[i], a[i - 1]);
		}
	}
	cout << c << endl;
	string temp = "";
	for (int i = 1; i < lenb; i++) {
		if (b[i] % 2 == b[i - 1] % 2) {
			temp += max(b[i], b[i - 1]);
		}
	}
	if (c != temp)cout << temp << endl;
}
int main() {
	ios, tie;
	solve();
	return 0;
}

L1-7 机工士姆斯塔迪奥 (20 分)

在 MMORPG《最终幻想14》的副本“乐欲之所瓯博讷修道院”里,BOSS 机工士姆斯塔迪奥将会接受玩家的挑战。
你需要处理这个副本其中的一个机制: N × M N×M N×M 大小的地图被拆分为了 $N×M $ 个 $ 1×1$ 的格子,BOSS 会选择若干行或/及若干列释放技能,玩家不能站在释放技能的方格上,否则就会被击中而失败。
给定 BOSS 所有释放技能的行或列信息,请你计算出最后有多少个格子是安全的。

输入格式:

输入第一行是三个整数 N , M , Q ( 1 ≤ N × M ≤ 1 0 5 , 0 ≤ Q ≤ 1000 ) N,M,Q (1≤N×M≤10^{5},0≤Q≤1000) N,M,Q(1N×M1050Q1000),表示地图为 N 行 M 列大小以及选择的行/列数量。
接下来 Q 行,每行两个数 Ti,Ci,其中 Ti=0 表示 BOSS 选择的是一整行,Ti=1 表示选择的是一整列,Ci 为选择的行号/列号。行和列的编号均从 1 开始。

输出格式:

输出一个数,表示安全格子的数量。

输入样例:

5 5 3
0 2
0 4
1 3

输出样例:

12

思路

没减少一次行则对于的n减一,列则对应的m减一。注意可能会给出相同的行列。

代码

#include<iostream>
#define endl '\n'
#define ios ios::sync_with_stdio(false)
#define tie cin.tie(nullptr),cout.tie(nullptr)
using namespace std;
const int maxn = 1e5 + 5;
int vis[2][maxn];
void solve() {
	long long n, m, q;
	cin >> n >> m >> q;
	while (q--) {
		int t, c; 
		cin >> t >> c;
		if (t) {
			if (!vis[t][c]) {
				vis[t][c] = true;
				m--;
			}
		}
		else {
			if (!vis[t][c]) {
				vis[t][c] = true;
				n--;
			}
		}
	}
	cout << n * m << endl;
}
int main() {
	ios, tie;
	solve();
	return 0;
}

L1-8 静静的推荐 (20 分)

天梯赛结束后,某企业的人力资源部希望组委会能推荐一批优秀的学生,这个整理推荐名单的任务就由静静姐负责。企业接受推荐的流程是这样的:

  • 只考虑得分不低于 175 分的学生;
  • 一共接受 K 批次的推荐名单;
  • 同一批推荐名单上的学生的成绩原则上应严格递增;
  • 如果有的学生天梯赛成绩虽然与前一个人相同,但其参加过 PAT 考试,且成绩达到了该企业的面试分数线,则也可以接受。

给定全体参赛学生的成绩和他们的 PAT 考试成绩,请你帮静静姐算一算,她最多能向企业推荐多少学生?

输入格式:

输入第一行给出 3 个正整数: N ( ≤ 1 0 5 ) N(≤10^{5}) N(105)为参赛学生人数, K ( ≤ 5 × 1 0 3 ) K(≤5×10^{3}) K(5×103)为企业接受的推荐批次, S ( ≤ 100 ) S(≤100) S(100)为该企业的 PAT 面试分数线。

随后 N 行,每行给出两个分数,依次为一位学生的天梯赛分数(最高分 290)和 PAT 分数(最高分 100)。

输出格式:

在一行中输出静静姐最多能向企业推荐的学生人数。

输入样例:

10 2 90
203 0
169 91
175 88
175 0
175 90
189 0
189 0
189 95
189 89
256 100

输出样例:

8

样例解释:

第一批可以选择 175、189、203、256 这四个分数的学生各一名,此外 175 分 PAT 分数达到 90 分的学生和 189 分 PAT 分数达到 95 分的学生可以额外进入名单。第二批就只剩下 175、189 两个分数的学生各一名可以进入名单了。最终一共 8 人进入推荐名单。

思路

构建结构体,将结构体按照先rank分数再pta分数排序。设置一个分数访问数组vis,每次进来一个未出现的分数则推荐,出现一个已经出现过的分数则判断该分数的pta分数是否符合要求。循环 k 次即可。
注意每次将没有推荐的放进另一个数组,否则超时。这里我用的循环数组。

代码

#include<iostream>
#include<algorithm>
#include<utility>
#include<vector>
#include<cstring>
#define endl '\n'
#define ios ios::sync_with_stdio(false)
#define tie cin.tie(nullptr),cout.tie(nullptr)
using namespace std;
using pii = pair<int, int>;
const int maxn = 1e5 + 5;
vector<pii> vpii[2];
bool vis[300];
bool check[maxn];
void solve() {
	int n, k, s;
	scanf("%d%d%d", &n, &k, &s);
	for (int i = 0, rank, pta; i < n; i++) {
		scanf("%d%d", &rank, &pta);
		if (rank >= 175)vpii[0].push_back({rank,pta});
	}
	sort(vpii[0].begin(), vpii[0].end());
	int ans = 0, dex = 0, len;
	while (k--) {
		memset(vis, false, sizeof vis);
		len = vpii[dex & 1].size();
		for (int i = 0; i < len; i++) {
			if (!vis[vpii[dex & 1][i].first]) {
				vis[vpii[dex & 1][i].first] = true;
				check[i] = true;
				++ans;
			}
			else if (vpii[dex & 1][i].second >= s) {
				check[i] = true;
				++ans;
			}
			else {
				vpii[!(dex & 1)].push_back(vpii[dex & 1][i]);
			}
		}
		vpii[dex & 1].clear();
		++dex;
	}
	cout << ans << endl;
}
int main() {
	ios, tie;
	solve();
	return 0;
}

L2-1 插松枝 (25 分)

在这里插入图片描述

人造松枝加工场的工人需要将各种尺寸的塑料松针插到松枝干上,做成大大小小的松枝。他们的工作流程(并不)是这样的:

  • 每人手边有一只小盒子,初始状态为空。
  • 每人面前有用不完的松枝干和一个推送器,每次推送一片随机型号的松针片。
  • 工人首先捡起一根空的松枝干,从小盒子里摸出最上面的一片松针 —— 如果小盒子是空的,就从推送器上取一片松针。将这片松针插到枝干的最下面。
  • 工人在插后面的松针时,需要保证,每一步插到一根非空松枝干上的松针片,不能比前一步插上的松针片大。如果小盒子中最上面的松针满足要求,就取之插好;否则去推送器上取一片。如果推送器上拿到的仍然不满足要求,就把拿到的这片堆放到小盒子里,继续去推送器上取下一片。注意这里假设小盒子里的松针片是按放入的顺序堆叠起来的,工人每次只能取出最上面(即最后放入)的一片。
  • 当下列三种情况之一发生时,工人会结束手里的松枝制作,开始做下一个:

(1)小盒子已经满了,但推送器上取到的松针仍然不满足要求。此时将手中的松枝放到成品篮里,推送器上取到的松针压回推送器,开始下一根松枝的制作。

(2)小盒子中最上面的松针不满足要求,但推送器上已经没有松针了。此时将手中的松枝放到成品篮里,开始下一根松枝的制作。

(3)手中的松枝干上已经插满了松针,将之放到成品篮里,开始下一根松枝的制作。

现在给定推送器上顺序传过来的 N 片松针的大小,以及小盒子和松枝的容量,请你编写程序自动列出每根成品松枝的信息。

输入格式:

输入在第一行中给出 3 个正整数:N(≤103),为推送器上松针片的数量;M(≤20)为小盒子能存放的松针片的最大数量;K(≤5)为一根松枝干上能插的松针片的最大数量。

随后一行给出 N 个不超过 100 的正整数,为推送器上顺序推出的松针片的大小。

输入格式:

输入在第一行中给出 3 个正整数:N(≤103),为推送器上松针片的数量;M(≤20)为小盒子能存放的松针片的最大数量;K(≤5)为一根松枝干上能插的松针片的最大数量。

随后一行给出 N 个不超过 100 的正整数,为推送器上顺序推出的松针片的大小。

输出格式:

每支松枝成品的信息占一行,顺序给出自底向上每片松针的大小。数字间以 1 个空格分隔,行首尾不得有多余空格。

输入样例:

8 3 4
20 25 15 18 20 18 8 5

输出样例:

20 15
20 18 18 8
25 5

思路

小盒子为栈,推送器和松枝为数组。按照题目要求按顺序模拟,注意输出格式。

代码

#include<iostream>
#include<vector>
#include<stack>
#define endl '\n'
#define ios ios::sync_with_stdio(false)
#define tie cin.tie(nullptr),cout.tie(nullptr)
using namespace std;
using pii = pair<int, int>;
const int maxn = 1e3 + 5;
vector<int> vi;
stack<int> si;
int arr[maxn];
void print() {
	int len = vi.size();
	for (int i = 0; i < len; i++) {
		cout << vi[i];
		if (i != len - 1)cout << ' ';
	}
	if (len)cout << endl;
	vi.clear();
}
void solve() {
	int n, m, k;
	cin >> n >> m >> k;
	for (int i = 0; i < n; i++)cin >> arr[i];
	for (int i = 0; i < n;) {
		if (vi.size() == k)print();
		if (vi.empty()) {
			if (si.empty())vi.push_back(arr[i++]);
			else {
				vi.push_back(si.top());
				si.pop();
			}
		}
		else {
			if (si.empty()) {
				if (arr[i] > *vi.rbegin())si.push(arr[i++]);
				else vi.push_back(arr[i++]);
			}
			else {
				if (si.top() > *vi.rbegin()) {
					if (arr[i] > *vi.rbegin()) {
						if (si.size() == m)print();
						else si.push(arr[i++]);
					}
					else vi.push_back(arr[i++]);
				}
				else {
					vi.push_back(si.top());
					si.pop();
				}
			}
		}
	}
	while (!si.empty()) {
		if (vi.size() == k)print();
		if (vi.empty()) {
			vi.push_back(si.top());
			si.pop();
		}
		else {
			if (si.top() > *vi.rbegin())print();
			else {
				vi.push_back(si.top());
				si.pop();
			}
		}
	}
	print();
}
int main() {
	ios, tie;
	solve();
	return 0;
}

L2-2 老板的作息表 (25 分)

在这里插入图片描述

新浪微博上有人发了某老板的作息时间表,表示其每天 4:30 就起床了。但立刻有眼尖的网友问:这时间表不完整啊,早上九点到下午一点干啥了?
本题就请你编写程序,检查任意一张时间表,找出其中没写出来的时间段。

输入格式:

输入第一行给出一个正整数 N,为作息表上列出的时间段的个数。随后 N 行,每行给出一个时间段,格式为:
hh:mm:ss - hh:mm:ss
其中 hhmmss 分别是两位数表示的小时、分钟、秒。第一个时间是开始时间,第二个是结束时间。题目保证所有时间都在一天之内(即从 00:00:0023:59:59);每个区间间隔至少 1 秒;并且任意两个给出的时间区间最多只在一个端点有重合,没有区间重叠的情况。

输出格式:

按照时间顺序列出时间表中没有出现的区间,每个区间占一行,格式与输入相同。题目保证至少存在一个区间需要输出。

输入样例:

8
13:00:00 - 18:00:00
00:00:00 - 01:00:05
08:00:00 - 09:00:00
07:10:59 - 08:00:00
01:00:05 - 04:30:00
06:30:00 - 07:10:58
05:30:00 - 06:30:00
18:00:00 - 19:00:00

输出样例:

04:30:00 - 05:30:00
07:10:58 - 07:10:59
09:00:00 - 13:00:00
19:00:00 - 23:59:59

思路

我们将接受的时间排序之后,设置一个全局时间temp为00:00:00。之后遍历时间数组,如果相同则说明没有空缺,如果不同则说明从temp到该时间空缺。然后将temp设为该时间,重复上述步骤。最后检查temp是否到达23:59:59即可。

代码

#include<iostream>
#include<vector>
#include<string>
#include<algorithm>
#define endl '\n'
#define ios ios::sync_with_stdio(false)
#define tie cin.tie(nullptr),cout.tie(nullptr)
using namespace std;
using pii = pair<int, int>;
void solve() {
	int n; cin >> n; cin.get();
	vector<string> vs(n);
	for (int i = 0; i < n; i++) {
		getline(cin, vs[i]);
	}
	sort(vs.begin(), vs.end());
	string temp = "00:00:00";
	for (int i = 0; i < n; i++) {
		if (temp != vs[i].substr(0, 8)) {
			cout << temp << " - " << vs[i].substr(0, 8) << endl;
		}
		temp = vs[i].substr(11, 8);
	}
	if (temp != "23:59:59") {
		cout << temp << " - " << "23:59:59" << endl;
	}
}
int main() {
	ios, tie;
	solve();
	return 0;
}

L2-3 龙龙送外卖 (25 分)

龙龙是“饱了呀”外卖软件的注册骑手,负责送帕特小区的外卖。帕特小区的构造非常特别,都是双向道路且没有构成环 —— 你可以简单地认为小区的路构成了一棵树,根结点是外卖站,树上的结点就是要送餐的地址。
每到中午 12 点,帕特小区就进入了点餐高峰。一开始,只有一两个地方点外卖,龙龙简单就送好了;但随着大数据的分析,龙龙被派了更多的单子,也就送得越来越累……
看着一大堆订单,龙龙想知道,从外卖站出发,访问所有点了外卖的地方至少一次(这样才能把外卖送到)所需的最短路程的距离到底是多少?每次新增一个点外卖的地址,他就想估算一遍整体工作量,这样他就可以搞明白新增一个地址给他带来了多少负担。

输入格式:

输入第一行是两个数 N 和 M (2 ≤ N ≤ 105, 1 ≤ M ≤ 105),分别对应树上节点的个数(包括外卖站),以及新增的送餐地址的个数。

接下来首先是一行 N 个数,第 i 个数表示第 i 个点的双亲节点的编号。节点编号从 1 到 N,外卖站的双亲编号定义为 −1。
接下来有 M 行,每行给出一个新增的送餐地点的编号 Xi。保证送餐地点中不会有外卖站,但地点有可能会重复。
为了方便计算,我们可以假设龙龙一开始一个地址的外卖都不用送,两个相邻的地点之间的路径长度统一设为 1,且从外卖站出发可以访问到所有地点。
注意:所有送餐地址可以按任意顺序访问,且完成送餐后无需返回外卖站。

输出格式:

对于每个新增的地点,在一行内输出题目需要求的最短路程的距离。

输入样例:

7 4
-1 1 1 1 2 2 3
5
6
2
4

输出样例:

2
4
4
6

思路

因为小区的结构是一颗树,那么如果按照题目要求跑完所有的点,我们跑的路径也是一棵树,此时只有深度最深的一条路径我们只走一次,其余路径我们都需要进行一次往返。那么我们先预处理好所有的点深度,然后每次都从该点出发,深搜寻找根节点或者已经走过的点,将路径*2。然后每次更新最深深度,减去最深深度即为题目答案。

代码

#include<iostream>
#include<vector>
#define endl '\n'
#define ios ios::sync_with_stdio(false)
#define tie cin.tie(nullptr),cout.tie(nullptr)
using namespace std;
const int maxn = 1e5 + 5;
int n, m, root, ans = 0;
int deep[maxn], fa[maxn];
bool vis[maxn];
vector<int> vi[maxn];
void dep(int dex,int depth) {
	deep[dex] = depth;
	for (int it : vi[dex]) {
		dep(it, depth + 1);
	}
}
void dfs(int dex) {
	if (vis[dex] || (dex == root)) return;
	else {
		vis[dex] = true;
		ans += 2;
		dfs(fa[dex]);
	}
}
void solve() {
	cin >> n >> m;
	for (int i = 1, to; i <= n; i++) {
		cin >> to;
		if (~to)vi[to].push_back(i), fa[i] = to;
		else root = i, fa[i] = i;
	}
	dep(root, 1);
	for (int i = 0, maxd = 0, dex; i < m; i++) {
		cin >> dex;
		if (!vis[dex]) {
			maxd = max(maxd, deep[dex]);
			dfs(dex);
		}
		cout << ans - maxd + 1 << endl;
	}
}
int main() {
	ios, tie;
	solve();
	return 0;
}

L2-4 大众情人 (25 分)

在这里插入图片描述

人与人之间总有一点距离感。我们假定两个人之间的亲密程度跟他们之间的距离感成反比,并且距离感是单向的。例如小蓝对小红患了单相思,从小蓝的眼中看去,他和小红之间的距离为 1,只差一层窗户纸;但在小红的眼里,她和小蓝之间的距离为 108000,差了十万八千里…… 另外,我们进一步假定,距离感在认识的人之间是可传递的。例如小绿觉得自己跟小蓝之间的距离为 2,则即使小绿并不直接认识小红,我们也默认小绿早晚会认识小红,并且因为跟小蓝很亲近的关系,小绿会觉得自己跟小红之间的距离为 1+2=3。当然这带来一个问题,如果小绿本来也认识小红,或者他通过其他人也能认识小红,但通过不同渠道推导出来的距离感不一样,该怎么算呢?我们在这里做个简单定义,就将小绿对小红的距离感定义为所有推导出来的距离感的最小值。

一个人的异性缘不是由最喜欢他/她的那个异性决定的,而是由对他/她最无感的那个异性决定的。我们记一个人 i 在一个异性 j 眼中的距离感为 Dij;将 i 的“异性缘”定义为 1 / m a x j ∈ S ( i ) D i j 1/max_{j∈S(i)}{D_{ij}} 1/maxjS(i)Dij,其中 S ( i ) S(i) S(i) 是相对于 i 的所有异性的集合。那么“大众情人”就是异性缘最好(值最大)的那个人。

本题就请你从给定的一批人与人之间的距离感中分别找出两个性别中的“大众情人”。

输入格式:

输入在第一行中给出一个正整数 N(≤500),为总人数。于是我们默认所有人从 1 到 N 编号。

随后 N 行,第 i 行描述了编号为 i 的人与其他人的关系,格式为:

性别 K 朋友1:距离1 朋友2:距离2 …… 朋友K:距离K

其中 性别 是这个人的性别,F 表示女性,M 表示男性;K(<N 的非负整数)为这个人直接认识的朋友数;随后给出的是这 K 个朋友的编号、以及这个人对该朋友的距离感。距离感是不超过 106 的正整数。

题目保证给出的关系中一定两种性别的人都有,不会出现重复给出的关系,并且每个人的朋友中都不包含自己。

输出格式:

第一行给出自身为女性的“大众情人”的编号,第二行给出自身为男性的“大众情人”的编号。如果存在并列,则按编号递增的顺序输出所有。数字间以一个空格分隔,行首尾不得有多余空格。

输入样例:

6
F 1 4:1
F 2 1:3 4:10
F 2 4:2 2:2
M 2 5:1 3:2
M 2 2:2 6:2
M 2 3:1 2:5

输出样例:

2 3
4

思路

典型的floyd板子,将人与人之间的最短距离算出来后,分别跑每一个人对于异性的距离感就行

代码

#include<iostream>
#include<utility>
#include<cstring>
#include<vector>
#include<algorithm>
#define endl '\n'
#define ios ios::sync_with_stdio(false)
#define tie cin.tie(nullptr),cout.tie(nullptr)
using namespace std;
using pii = pair<int, int>;
const int maxn = 505;
const int inf = 0x3f3f3f3f;
int n, maze[maxn][maxn];
vector<pii> vi[maxn], ans;
vector<int> f, m;
char sex;
void floyd() {
	for (int k = 1; k <= n; k++) {
		for (int i = 1; i <= n; i++) {
			for (int j = 1; j <= n; j++) {
				maze[i][j] = min(maze[i][j], maze[i][k] + maze[k][j]);
			}
		}
	}
}
void solve() {
	cin >> n;
	memset(maze, 0x3f, sizeof maze);
	for (int i = 1, k, to, ra; i <= n; i++) {
		cin >> sex >> k;
		if (sex == 'F')f.push_back(i);
		else m.push_back(i);
		while (k--) {
			cin >> to; cin.get(); cin >> ra;
			maze[i][to] = ra;
		}
		maze[i][i] = 0;
	}
	floyd();
	for (int i : f) {
		int b = 0;
		for (int j : m) {
			b = max(b, maze[j][i]);
		}
		ans.push_back({ b,i });
	}
	sort(ans.begin(), ans.end());
	int len = ans.size();
	cout << ans[0].second;
	for (int i = 1; i < len; i++) {
		if (ans[i].first == ans[0].first)cout << ' ' << ans[i].second;
		else break;
	}
	cout << endl;
	ans.clear();
	for (int i : m) {
		int b = 0;
		for (int j : f) {
			b = max(b, maze[j][i]);
		}
		ans.push_back({ b,i });
	}
	sort(ans.begin(), ans.end());
	len = ans.size();
	cout << ans[0].second;
	for (int i = 1; i < len; i++) {
		if (ans[i].first == ans[0].first)cout << ' ' << ans[i].second;
		else break;
	}
}
int main() {
	ios, tie;
	solve();
	return 0;
}

L3-1 千手观音 (30 分)

在这里插入图片描述

人类喜欢用 10 进制,大概是因为人类有一双手 10 根手指用于计数。于是在千手观音的世界里,数字都是 10 000 进制的,因为每位观音有 1 000 双手 ……

千手观音们的每一根手指都对应一个符号(但是观音世界里的符号太难画了,我们暂且用小写英文字母串来代表),就好像人类用自己的 10 根手指对应 0 到 9 这 10 个数字。同样的,就像人类把这 10 个数字排列起来表示更大的数字一样,ta们也把这些名字排列起来表示更大的数字,并且也遵循左边高位右边低位的规则,相邻名字间用一个点 . 分隔,例如 pat.pta.cn 表示千手观音世界里的一个 3 位数。

人类不知道这些符号代表的数字的大小。不过幸运的是,人类发现了千手观音们留下的一串数字,并且有理由相信,这串数字是从小到大有序的!于是你的任务来了:请你根据这串有序的数字,推导出千手观音每只手代表的符号的相对顺序。

注意:有可能无法根据这串数字得到全部的顺序,你只要尽量推出能得到的结果就好了。当若干根手指之间的相对顺序无法确定时,就暂且按它们的英文字典序升序排列。例如给定下面几个数字:

pat
cn
lao.cn
lao.oms
pta.lao
pta.pat
cn.pat

我们首先可以根据前两个数字推断 pat < cn;根据左边高位的顺序可以推断 lao < pta < cn;再根据高位相等时低位的顺序,可以推断出 cn < omslao < pat。综上我们得到两种可能的顺序:lao < pat < pta < cn < oms;或者 lao < pta < pat < cn < oms,即 patpta 之间的相对顺序无法确定,这时我们按字典序排列,得到 lao < pat < pta < cn < oms

输入格式:

输入第一行给出一个正整数 N (≤105),为千手观音留下的数字的个数。随后 N 行,每行给出一个千手观音留下的数字,不超过 10 位数,每一位的符号用不超过 3 个小写英文字母表示,相邻两符号之间用 . 分隔。

我们假设给出的数字顺序在千手观音的世界里是严格递增的。题目保证数字是 104 进制的,即符号的种类肯定不超过 104 种。

输出格式:

在一行中按大小递增序输出符号。当若干根手指之间的相对顺序无法确定时,按它们的英文字典序升序排列。符号间仍然用 . 分隔。

输入样例:

7
pat
cn
lao.cn
lao.oms
pta.lao
pta.pat
cn.pat

输出样例:

lao.pat.pta.cn.oms

L3-2 关于深度优先搜索和逆序对的题应该不会很难吧这件事 (30 分)

背景知识

深度优先搜索与 DFS 序

深度优先搜索算法(DFS)是一种用于遍历或搜索树或图的算法。以下伪代码描述了在树 T 上进行深度优先搜索的过程:

procedure DFS(T, u, L)      // T 是被深度优先搜索的树
                            // u 是当前搜索的节点
                            // L 是一个链表,保存了所有节点被第一次访问的顺序
  append u to L             // 将节点 u 添加到链表 L 的末尾
  for v in u.children do    // 枚举节点 u 的所有子节点 v
    DFS(T, v)               // 递归搜索节点 v

r 为树 T 的根,调用 DFS(T, r, L) 即可完成对 T 的深度优先搜索,保存在链表 L 中的排列被称为 DFS 序。相信聪明的你已经发现了,如果枚举子节点的顺序不同,最终得到的 DFS 序也会不同。

逆序对

给定一个长度为 n 的整数序列 a 1 , a 2 , ⋯ , a n a_1,a_2,⋯,a_n a1,a2,,an,该序列的逆序对数量是同时满足以下条件的有序数对 (i,j) 的数量:

  • 1 ≤ i < j ≤ n 1≤i<j≤n 1i<jn
  • a i > a j a_i>a_j ai>aj

问题求解

给定一棵 n 个节点的树,其中节点 r 为根。求该树所有可能的 DFS 序中逆序对数量之和。

输入格式

第一行输入两个整数 n , r ( 2 ≤ n ≤ 3 × 1 0 5 , 1 ≤ r ≤ n ) n,r(2≤n≤3×10^5,1≤r≤n) nr2n3×1051rn 表示树的大小与根节点。

对于接下来的 (n−1) 行,第 i 行输入两个整数 u i u_i ui v i ( 1 ≤ u i , v i ≤ n ) v_i(1≤u_i,v_i≤n) vi(1ui,vin),表示树上有一条边连接节点 u i u_i ui v i v_i vi

输出格式

输出一行一个整数,表示该树所有可能的 DFS 序中逆序对数量之和。由于答案可能很大,请对 109+7 取模后输出。

样例输入 1

5 3
1 5
2 5
3 5
4 3

样例输出 1

24

样例输入 2

10 5
10 2
2 5
10 7
7 1
7 9
4 2
3 10
10 8
3 6

样例输出 2

516

样例解释

下图展示了样例 1 中的树。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6ylTs4FH-1650990542685)(C:\Users\29270\AppData\Roaming\Typora\typora-user-images\image-20220427002549868.png)]

该树共有 4 种可能的 DFS 序:

  • 3 , 4 , 5 , 1 , 2 {3,4,5,1,2} 3,4,5,1,2,有 6 个逆序对;
  • 3 , 4 , 5 , 2 , 1 {3,4,5,2,1} 3,4,5,2,1,有 7 个逆序对;
  • 3 , 5 , 1 , 2 , 4 {3,5,1,2,4} 3,5,1,2,4,有 5 个逆序对;
  • 3 , 5 , 2 , 1 , 4 {3,5,2,1,4} 3,5,2,1,4,有 6 个逆序对。

因此答案为 6 + 7 + 5 + 6 = 24 6+7+5+6=24 6+7+5+6=24

L3-3 教科书般的亵渎 (30 分)

九条可怜最近在玩一款卡牌游戏。在每一局游戏中,可怜都要使用抽到的卡牌来消灭一些敌人。每一名敌人都有一个初始血量,而当血量降低到 0 及以下的时候,这名敌人就会立即被消灭并从场上消失。

现在,可怜面前有 n 个敌人,其中第 i 名敌人的血量是 ai,而可怜手上只有如下两张手牌:

  1. 如果场上还有敌人,等概率随机选中一个敌人并对它造成一点伤害(即血量减 1),重复 K 次。
  2. 对所有敌人造成一点伤害,重复该效果直到没有新的敌人被消灭。

下面是这两张手牌效果的一些示例:

  1. 假设存在两名敌人,他们的血量分别是 1 , 2 1,2 1,2 K = 2 K=2 K=2。那么在可怜打出第一张手牌后,可能会发生如下情况:
  • 第一轮中,两名敌人各有 0.5 的概率被选中。假设第一名敌人被选中,那么它会被造成一点伤害。这时它的血量变成了 0,因此它被消灭并消失了。
  • 第二轮中,因为场上只剩下了第二名敌人,所以它一定会被选中并被造成一点伤害。这时它剩下的血量为 1。
  1. 同样假设存在两名敌人且血量分别为 1,2。那么在可怜打出第二张手牌后,会发生如下情况:
  • 第一轮中,所有敌人被造成了一点伤害。这时第一名敌人被消灭了,因此卡牌效果会被重复一遍。
  • 第二轮中,所有敌人(此时只剩下第二名敌人了)被造成了一点伤害。这时第二名敌人也被消灭了,因此卡牌效果会被再重复一遍。
  • 第三轮中,所有敌人(此时没有敌人剩下了)被造成了一点伤害。因为没有新的敌人被消灭了,所以卡牌效果结束。
  1. 如果面对的是四名血量分别为 1 , 2 , 2 , 4 1,2,2,4 1,2,2,4 的敌人,那么在可怜打出第二张手牌后,只有第四名敌人还会存活,且它的剩余血量为 1。

现在,可怜先打出了第一张手牌,再打出了第二张手牌。她发现,在第一张手牌效果结束后,没有任何一名敌人被消灭,但是在第二张手牌的效果结束后,所有敌人都被消灭了。

可怜想让你计算一下这种情况发生的概率是多少。

输入格式:

第一行输入两个整数 n,K(1≤n,K≤50),分别表示敌人的数量以及第一张卡牌效果的发动次数。
第二行输入 n 个由空格隔开的整数 ai(1≤ai≤50),表示每个敌人的初始血量。

输出格式:

在一行中输出一个整数,表示发生概率对 998244353 取模后的结果。

具体来说,如果概率的最简分数表示为 a/b(a≥0,b≥1,gcd(a,b)=1),那么你需要输出

a×b998244351mod998244353。

输入样例 1:

3 2
2 3 3

输出样例 1:

665496236

样例解释 1:

在第一张手牌的效果结束后,三名敌人的剩余血量只可能在如下几种中:[1,3,2], [1,2,3], [2,1,3] 和 [2,3,1]。前两种发生的概率是 2/9,后两种发生的概率是 1/9。因此答案为 2/3,输出 2×3998244351mod998244353=665496236。

输入样例 2:

3 3
2 3 3

输出样例 2:

776412275

样例解释 2:

在第一张手牌的效果结束后,三名敌人的剩余血量只可能在如下几种中:[1,2,2]、[2,1,2] 和 [2,2,1]。第一种发生的概率是 2/9,后两种发生的概率是 1/9。因此答案为 4/9,输出 4×9998244351mod998244353=776412275。

输入样例 3:

5 3
1 4 4 2 5

输出样例 3:

367353922

输入样例 4:

12 12
1 2 3 4 5 6 7 8 9 10 11 12

输出样例 4:

452061016

未完待续

  • 5
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值