2024 年 GPLT 团体程序设计天梯赛(个人感受 + 题解)
前言
去年第一次参加天梯赛,拿了 \(158\) 分,没有个人奖,团队也差点打铁(最后应该是递补省三)。
今年天梯赛拿了 \(224\) 分,幸运地拿了个人国二。这次担任的队长,团队也拿到了国二,感谢队友们的努力付出。
今年好像很多人断网,不过没有发生在我身上哈哈哈(也许是因为我连的手机热点)。对于我个人来说,比赛过程还是比较顺畅的,题目难度个人认为比去年简单(不过部分题目有点...抽象?),最后结果还算满意。倒数第二题据说暴力都可以拿 \(21\) 分,我当时做完 \(\text{L3-1}\) 的时候还剩大概二十分钟,然后就...笑了差不多二十分钟,也许真应该继续做下倒数第二题的(虽然好像打了暴力也不会国一🤡)。
总得来说结果还算不错,比去年进步很多,队友们还是很给力的。我的学校算法竞赛一直以来挺弱的,这次天梯赛算是一个好的开头,我相信今后学校会越来越强的!
今年也许是我最后一次参加天梯赛了,当然到了大四想继续玩也不是不行(如果大佬学弟们不嫌弃我的话😋)。
L1-1 编程解决一切
题目描述
编程解决一切 —— 本题非常简单,就请你直接在屏幕上输出这句话:\(\text{“Problem? The Solution: Programming.”}\)。
输入格式
无
输出格式
在一行中输出 Problem? The Solution: Programming.
。
解题思路
签到题。
#include<bits/stdc++.h>
using namespace std;
#define ios ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
int main(){
ios;
puts("Problem? The Solution: Programming.");
return 0;
}
L1-2 再进去几个人
题目描述
题干太长了不想看,随便输出 \(B - A\) 就行。
输入格式
输入在一行中给出 2 个不超过 100 的正整数 \(A\) 和 \(B\),其中 \(A\) 是进去的人数,\(B\) 是出来的人数。题目保证 \(B\) 比 \(A\) 要大。
输出格式
在一行中输出使得房子变空的、需要再进去的人数。
测试数据
输入样例
4 7
输出样例
3
解题思路
输出 \(B - A\) 即可。
#include<bits/stdc++.h>
using namespace std;
#define ios ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
int main(){
ios;
int a, b; cin >> a >> b;
cout << b - a << "\n";
return 0;
}
L1-3 帮助色盲
题目描述
又是题干老长的一道题,赛场上读题的时候差点被绕晕了。大概意思就是说:
-
前方有行人
第一行输出
-
即可。-
此时如果是绿灯,第二行输出
move
-
此时如果不是绿灯,第二行输出
stop
-
-
前方无行人
-
如果是绿灯,第一行输出
dudu
,第二行输出move
-
如果是红灯,第一行输出
biii
,第二行输出stop
-
如果是黄灯,第一行输出
-
,第二行输出stop
-
输入格式
输入在一行中给出两个数字 \(A\) 和 \(B\),其间以空格分隔。其中 \(A\) 是当前交通灯的颜色,取值为 \(0\) 表示红灯、\(1\) 表示绿灯、\(2\) 表示黄灯;\(B\) 是前方行人的状态,取值为 \(0\) 表示前方两米内没有同向行走的人、\(1\) 表示有。
输出格式
根据输入的状态在第一行中输出提示音:dudu
表示前方为绿灯,可以继续前进;biii
表示前方为红灯,应该止步;-
表示不做提示。在第二行输出患者应该执行的动作:move
表示继续前进、stop
表示止步。
测试数据
输入样例 1
0 0
输出样例 1
biii
stop
输入样例 2
1 1
输出样例 2
-
move
解题思路
按照题意判断输出结果即可。
#include<bits/stdc++.h>
using namespace std;
#define ios ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
int main(){
ios;
int a, b; cin >> a >> b;
if(b == 1){
cout << "-" << "\n";
cout << (a == 1 ? "move" : "stop") << "\n";
}else{
if(a == 0) cout << "biii\nstop" << "\n";
else if(a == 1) cout << "dudu\nmove" << "\n";
else cout << "-\nstop" << "\n";
}
return 0;
}
L1-4 四项全能
题目描述
给定全班总人数为 \(n\),有 \(m\) 项技能,每个技能分别有 \(k_1, k_2, \ldots , k_m\) 个人会,求至少有多少人会所有的 \(m\) 项技能。
输入格式
输入在第一行中给出 2 个正整数:\(n\)( \(4 \le n \le 1000\))和 \(m\)(\(1 < m \le \frac{n}{2}\)),分别对应全班人数和技能总数。随后一行给出 \(m\) 个不超过 \(n\) 的正整数,其中第 \(i\) 个整数对应会第 \(i\) 项技能的人数。
输出格式
输出至少有多少人 \(m\) 项都会。
测试数据
输入样例
50 4
30 35 42 46
输出样例
3
解题思路
小学数学题(但是我不会),题干中给出了一般情况的求解方法。 但有一些情况需要特判,例如当每个技能会的人的总数恰好等于 \(n \times m\) 时,此时所有人都会所有技能,故需要输出 \(n\);又例如当人数 \(n\) 大于每个技能会的人的总数时,此时无人会所有技能,故需要输出 \(0\)。
这题我赛场上没拿满分哈哈哈😋
#include<bits/stdc++.h>
using namespace std;
#define ios ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
int main(){
ios;
int n, m, sum = 0; cin >> n >> m;
for(int i = 1, x; i <= m; i ++ ) cin >> x, sum += x;
if(sum == n * m) cout << n << "\n"; // 所有人都会m个
else if(n > sum) cout << 0 << "\n"; // 人个数大于总技能数,此时无人全会
else cout << sum - (sum / n) * n << "\n"; // 题干中的情况
return 0;
}
L1-5 别再来这么多猫娘了!
题目描述
你会得到一段由大小写字母、数字、空格及 ASCII 码范围内的标点符号的文字,以及若干个违禁词以及警告阈值,你需要首先检查内容里有多少违禁词,如果少于阈值个,则简单地将违禁词替换为 <censored>
;如果大于等于阈值个,则直接输出一段警告并输出有几个违禁词。
输入格式
输入第一行是一个正整数 \(N\) (\(1\le N \le 100\)),表示违禁词的数量。接下来的 \(N\) 行,每行一个长度不超过 10 的、只包含大小写字母、数字及 ASCII 码范围内的标点符号的单词,表示应当屏蔽的违禁词。
然后的一行是一个非负整数 \(k\) (\(0\le k\le 100\)),表示违禁词的阈值。
最后是一行不超过 5000 个字符的字符串,表示需要检查的文字。
从左到右处理文本,违禁词则按照输入顺序依次处理;对于有重叠的情况,无论计数还是替换,查找完成后从违禁词末尾继续处理。
输出格式
如果违禁词数量小于阈值,则输出替换后的文本;否则先输出一行一个数字,表示违禁词的数量,然后输出 He Xie Ni Quan Jia!
。
测试数据
输入样例1
5
MaoNiang
SeQing
BaoLi
WeiGui
BuHeShi
4
BianCheng MaoNiang ba! WeiGui De Hua Ye Keyi Shuo! BuYao BaoLi NeiRong.
输出样例1
BianCheng <censored> ba! <censored> De Hua Ye Keyi Shuo! BuYao <censored> NeiRong.
输入样例2
5
MaoNiang
SeQing
BaoLi
WeiGui
BuHeShi
3
BianCheng MaoNiang ba! WeiGui De Hua Ye Keyi Shuo! BuYao BaoLi NeiRong.
输出样例2
3
He Xie Ni Quan Jia!
输入样例3
2
AA
BB
3
AAABBB
输出样例3
<censored>A<censored>B
输入样例4
2
AB
BB
3
AAABBB
输出样例4
AA<censored><censored>
输入样例5
2
BB
AB
3
AAABBB
输出样例5
AAA<censored>B
解题思路
这个题...真的一言难尽,赛场上超时了两个点,一度以为是方法问题,而实际上是因为违禁词中可能存在 <censored>
的子串,从而在替换过程中导致无限替换下去。因此只需要先将违禁词替换成一个奇怪的东西,最后再全部替换回 <censored>
即可。
#include<bits/stdc++.h>
using namespace std;
#define ios ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
vector<string> ban;
int n, k;
string s, tmp = "*****";
int main(){
ios;
cin >> n;
ban.resize(n);
for(int i = 0; i < n; i ++ ) cin >> ban[i];
cin >> k;
cin.ignore();
getline(cin, s);
int cnt = 0;
for(auto &t : ban){
int sz = t.size(), pos = s.find(t);
while(pos != -1){
cnt ++ ;
s = s.substr(0, pos) + tmp + s.substr(pos + sz);
pos = s.find(t);
}
}
if(cnt >= k) cout << cnt << '\n' << "He Xie Ni Quan Jia!" << "\n";
else{
int sz = tmp.size(), pos = s.find(tmp);
while(pos != -1){
s = s.substr(0, pos) + "<censored>" + s.substr(pos + sz);
pos = s.find(tmp);
}
cout << s << "\n";
}
return 0;
}
L1-6 兰州牛肉面
题目描述
请你写程序帮助一家牛肉面馆的老板统计一下,他们一天卖出各种品种的牛肉面有多少碗,营业额一共有多少。
输入格式
输入第一行给出一个正整数 \(N\)(\(\le 100\)),为牛肉面的种类数量。这里为了简单起见,我们把不同种类的牛肉面从 \(1\) 到 \(N\) 编号,以后就用编号代替牛肉面品种的名称。第二行给出 \(N\) 个价格,第 \(i\) 个价格对应第 \(i\) 种牛肉面一碗的单价。这里的价格是 \([0.01, 200.00]\) 区间内的实数,以元为单位,精确到分。
随后是一天内客人买面的记录,每条记录占一行,格式为:
品种编号 碗数
其中 碗数 保证是正整数。当对应的 品种编号 为 \(0\) 时,表示输入结束。这个记录不算在内。
输出格式
首先输出 \(N\) 行,第 \(i\) 行输出第 \(i\) 种牛肉面卖出了多少碗。最后一行输出当天的总营业额,仍然是以元为单位,精确到分。题目保证总营业额不超过 \(10^6\)。
测试数据
输入样例
5
4.00 8.50 3.20 12.00 14.10
3 5
5 2
1 1
2 3
2 2
1 9
0 0
输出样例
10
5
5
0
2
126.70
解题思路
统计每个种类的个数,同时统计总营业额,按照要求输出结果即可。
#include<bits/stdc++.h>
using namespace std;
#define ios ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
int main(){
// ios;
int n; cin >> n;
vector<double> a(n + 1);
vector<int> cnt(n + 1, 0);
for(int i = 1; i <= n; i ++ ) cin >> a[i];
double sum = 0;
int x, y;
while(cin >> x >> y, x){
cnt[x] += y;
sum += a[x] * y;
}
for(int i = 1; i <= n; i ++ ) cout << cnt[i] << "\n";
printf("%.2lf\n", sum);
return 0;
}
L1-7 整数的持续性
题目描述
从任一给定的正整数 \(n\) 出发,将其每一位数字相乘,记得到的乘积为 \(n_1\)。以此类推,令 \(n_{i+1}\) 为 \(n_i\) 的各位数字的乘积,直到最后得到一个个位数 \(n_m\),则 \(m\) 就称为 \(n\) 的持续性。
本题就请你编写程序,找出任一给定区间内持续性最长的整数。
输入格式
输入在一行中给出两个正整数 \(a\) 和 \(b\)(\(1\le a \le b\le 10^9\) 且 \((b−a) < 10^3\)),为给定区间的两个端点。
输出格式
首先在第一行输出区间 \([a,b]\) 内整数最长的持续性。随后在第二行中输出持续性最长的整数。如果这样的整数不唯一,则按照递增序输出,数字间以 \(1\) 个空格分隔,行首尾不得有多余空格。
测试数据
输入样例
500 700
输出样例
5
679 688 697
解题思路
按照题意模拟,统计结果即可。
#include<bits/stdc++.h>
using namespace std;
#define ios ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
int work(int x){
int res = 0;
while(x > 10){
int tmp = 1;
while(x) tmp *= x % 10, x /= 10;
x = tmp;
res ++ ;
}
return res;
}
int main(){
ios;
int l, r; cin >> l >> r;
vector<int> ans;
int mx = 0;
for(int i = l; i <= r; i ++ ){
int cur = work(i);
if(cur > mx) mx = cur, ans.clear(), ans.emplace_back(i);
else if(cur == mx) ans.emplace_back(i);
}
cout << mx << "\n";
for(int i = 0; i < (int)ans.size(); i ++ ) cout << ans[i] << " \n"[i == (int)ans.size() - 1];
return 0;
}
L1-8 九宫格
题目描述
九宫格是一款数字游戏,传说起源于河图洛书,现代数学中称之为三阶幻方。游戏规则是:将一个 \(9\times 9\) 的正方形区域划分为 \(9\) 个 \(3\times 3\) 的正方形宫位,要求 \(1\) 到 \(9\) 这九个数字中的每个数字在每一行、每一列、每个宫位中都只能出现一次。
本题并不要求你写程序解决这个问题,只是对每个填好数字的九宫格,判断其是否满足游戏规则的要求。
输入格式
输入首先在第一行给出一个正整数 \(n\)(\(\le 10\)),随后给出 \(n\) 个填好数字的九宫格。每个九宫格分 \(9\) 行给出,每行给出 \(9\) 个数字,其间以空格分隔。
输出格式
对每个给定的九宫格,判断其中的数字是否满足游戏规则的要求。满足则在一行中输出 \(1\),否则输出 \(0\)。
测试数据
输入样例
3
5 1 9 2 8 3 4 6 7
7 2 8 9 6 4 3 5 1
3 4 6 5 7 1 9 2 8
8 9 2 1 4 5 7 3 6
4 7 3 6 2 8 1 9 5
6 5 1 7 3 9 2 8 4
9 3 4 8 1 6 5 7 2
1 6 7 3 5 2 8 4 9
2 8 5 4 9 7 6 1 3
8 2 5 4 9 7 1 3 6
7 9 6 5 1 3 8 2 4
3 4 1 6 8 2 7 9 5
6 8 4 2 7 1 3 5 9
9 1 2 8 3 5 6 4 7
5 3 7 9 6 4 2 1 8
2 7 9 1 5 8 4 6 3
4 5 8 3 2 6 9 7 1
1 6 3 7 4 9 5 8 3
81 2 5 4 9 7 1 3 6
7 9 6 5 1 3 8 2 4
3 4 1 6 8 2 7 9 5
6 8 4 2 7 1 3 5 9
9 1 2 8 3 5 6 4 7
5 3 7 9 6 4 2 1 8
2 7 9 1 5 8 4 6 3
4 5 8 3 2 6 9 7 1
1 6 3 7 4 9 5 8 2
输出样例
1
0
0
解题思路
\(1\) 到 \(9\) 这九个数字中的每个数字在 每一行、每一列、每个宫位 中都只能出现一次。 关键是恰好一次,不能出现其他数字(样例也给出了这个特例)。写几个东西模拟判断一下就行,很简单(比较菜,写得稍微有点长🥵)。
#include<bits/stdc++.h>
using namespace std;
#define ios ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
const int N = 10;
int g[N][N], n = 9;
bool check1(){ // 每一行
for(int i = 1; i <= n; i ++ ){
vector<bool> vis(10, false);
for(int j = 1; j <= n; j ++ ){
if(g[i][j] < 1 || g[i][j] > 9) return false;
if(vis[g[i][j]]) return false;
vis[g[i][j]] = true;
}
for(int i = 1; i <= 9; i ++ ) if(!vis[i]) return false;
}
return true;
}
bool check2(){ // 每一列
for(int i = 1; i <= n; i ++ ){
vector<bool> vis(10, false);
for(int j = 1; j <= n; j ++ ){
if(g[j][i] < 1 || g[j][i] > 9) return false;
if(vis[g[j][i]]) return false;
vis[g[j][i]] = true;
}
for(int i = 1; i <= 9; i ++ ) if(!vis[i]) return false;
}
return true;
}
bool check3(){ // 每一个九宫格
for(int sx = 1; sx <= n; sx += 3)
for(int sy = 1; sy <= n; sy += 3){
vector<bool> vis(10, false);
for(int i = sx; i <= sx + 2; i ++ )
for(int j = sy; j <= sy + 2; j ++ ){
if(g[i][j] < 1 || g[i][j] > 9) return false;
if(vis[g[i][j]]) return false;
vis[g[i][j]] = true;
}
for(int i = 1; i <= 9; i ++ ) if(!vis[i]) return false;
}
return true;
}
int main(){
ios;
int T; cin >> T;
while(T -- ){
for(int i = 1; i <= n; i ++ )
for(int j = 1; j <= n; j ++ )
cin >> g[i][j];
cout << (check1() && check2() && check3() ? "1" : "0") << "\n";
}
return 0;
}
L2-1 鱼与熊掌
题目描述
给定 \(n\) 个人对 \(m\) 种物品的拥有关系。对其中任意一对物品种类,请你统计有多少人能够兼得?
输入格式
输入首先在第一行给出 2 个正整数,分别是:\(n\)(\(\le 10^5\))为总人数(所有人从 \(1\) 到 \(n\) 编号)、\(m\)(\(2\le m\le 10^5\))为物品种类的总数(所有物品种类从 \(1\) 到 \(m\) 编号)。随后 \(n\) 行,第 \(i\) 行(\(1\le i\le n\))给出编号为 \(i\) 的人所拥有的物品种类清单,格式为:
K M[1] M[2] ... M[K]
其中 \(K\)(\(\le 10^3\))是该人拥有的物品种类数量,后面的 \(M[*]\) 是物品种类的编号。题目保证每个人的物品种类清单中都没有重复给出的种类。
最后是查询信息:首先在一行中给出查询总量 \(Q\)(\(\le 100\)),随后 \(Q\) 行,每行给出一对物品种类编号,其间以空格分隔。题目保证物品种类编号都是合法存在的。
输出格式
对每一次查询,在一行中输出两种物品兼得的人数。
测试数据
输入样例
4 8
3 4 1 8
4 7 1 8 4
5 6 5 1 2 3
4 3 2 4 8
3
2 3
7 6
8 4
输出样例
2
0
3
解题思路
大水题,数据很弱。直接用 set
存储每个物品对应的人,对于每个询问直接暴力判断即可。
#include<bits/stdc++.h>
using namespace std;
#define ios ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
const int N = 1e5 + 10;
set<int> st[N];
int n, m, q;
int main(){
ios;
cin >> n >> m;
for(int i = 1, k; i <= n; i ++ ){
cin >> k;
for(int j = 1, x; j <= k; j ++ ) cin >> x, st[x].insert(i);
}
cin >> q;
for(int i = 1, a, b; i <= q; i ++ ){
cin >> a >> b;
int cnt = 0;
for(auto &x : st[a]) cnt += (st[b].count(x));
cout << cnt << "\n";
}
return 0;
}
L2-2 懂蛇语
题目描述
首先提取 A 的每个字的首字母,然后把整句话替换为另一句话 B,B 中每个字的首字母与 A 中提取出的字母依次相同。例如二当家说“九点下班哈”,对应首字母缩写是 JDXBH,他们解释为实际想说的是“京东新百货”……
本题就请你写一个蛇语的自动翻译工具,将输入的蛇语转换为实际要表达的句子。
输入格式
输入第一行给出一个正整数 \(N\)(\(\le 10^5\)),为蛇语词典中句子的个数。随后 \(N\) 行,每行用汉语拼音给出一句话。每句话由小写英文字母和空格组成,每个字的拼音由不超过 6 个小写英文字母组成,两个字的拼音之间用空格分隔。题目保证每句话总长度不超过 50 个字符,用回车结尾。注意:回车不算句中字符。
随后在一行中给出一个正整数 \(M\)(\(\le 10^3\)),为查询次数。后面跟 \(M\) 行,每行用汉语拼音给出需要查询的一句话,格式同上。
输出格式
对每一句查询,在一行中输出其对应的句子。如果句子不唯一,则按整句的字母序输出,句子间用 |
分隔。如果查不到,则将输入的句子原样输出。
注意:输出句子时,必须保持句中所有字符不变,包括空格。
测试数据
输入样例
8
yong yuan de shen
yong yuan de she
jing dong xin bai huo
she yu wo ye hui shuo yi dian dian
liang wei bu yao chong dong
yi dian dian
ni hui shuo she yu a
yong yuan de sha
7
jiu dian xia ban ha
shao ye wu ya he shui you dian duo
liu wan bu yao ci dao
ni hai shi su yan a
yao diao deng
sha ye ting bu jian
y y d s
输出样例
jing dong xin bai huo
she yu wo ye hui shuo yi dian dian
liang wei bu yao chong dong
ni hui shuo she yu a
yi dian dian
sha ye ting bu jian
yong yuan de sha|yong yuan de she|yong yuan de shen
解题思路
用 map<string, vector<string> >
存储每个首字母构成的串对应的所有句子,对于每个询问,将询问的句子处理成首字母构成的串,按照要求输出结果即可。
对于首字母的判断,我给字符串前面加了一个空格,这样只要是前面一个位置是空格的字符,就是首字母。
如果有多个对应,需要按照字典序输出结果,所以排个序就好了。
#include<bits/stdc++.h>
using namespace std;
#define ios ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
int n, q;
map<string, vector<string> > mp;
string s;
inline string work(string s){
string res = "";
int sz = s.size();
s = " " + s; // 方便首字母判断
for(int i = 1; i <= sz; i ++ )
if(isalpha(s[i]) && s[i - 1] == ' ') res += s[i]; // 前一个时空格的字符为首字母
return res;
}
int main(){
ios;
cin >> n;
cin.ignore();
for(int i = 1; i <= n; i ++ ){
getline(cin, s);
mp[work(s)].emplace_back(s);
}
for(auto &[_, v] : mp) sort(begin(v), end(v)); // 要排序
cin >> q;
cin.ignore();
for(int i = 1; i <= q; i ++ ){
getline(cin, s);
string t = work(s);
if(!mp.count(t)) cout << s << "\n";
else{
int sz = mp[t].size();
for(int i = 0; i < sz; i ++ ){
if(i > 0) cout << "|";
cout << mp[t][i];
}
cout << "\n";
}
}
return 0;
}
L2-3 满树的遍历
题目描述
一棵“\(k\) 阶满树”是指树中所有非叶结点的度都是 \(k\) 的树。给定一棵树,你需要判断其是否为 \(k\) 阶满树,并输出其 前序遍历 序列。
注:树中结点的度是其拥有的子树的个数,而树的度是树内各结点的度的最大值。
输入格式
输入首先在第一行给出一个正整数 \(n\)(\(\le 10^5\)),是树中结点的个数。于是设所有结点从 \(1\) 到 \(n\) 编号。
随后 \(n\) 行,第 \(i\) 行(\(1\le i \le n\))给出第 \(i\) 个结点的父结点编号。根结点没有父结点,则对应的父结点编号为 \(0\)。题目保证给出的是一棵合法多叉树,只有唯一根结点。
输出格式
首先在一行中输出该树的度。如果输入的树是 \(k\) 阶满树,则加 1 个空格后输出 yes
,否则输出 no
。最后在第二行输出该树的前序遍历序列,数字间以 1 个空格分隔,行首尾不得有多余空格。
注意:兄弟结点按编号升序访问。
测试数据
输入样例 1
7
6
5
5
6
6
0
5
输出样例 1
3 yes
6 1 4 5 2 3 7
输入样例 2
7
6
5
5
6
6
0
4
输出样例 2
3 no
6 1 4 7 5 2 3
解题思路
又是一个大水题。按照题意建树,统计一下度数,维护一下最大度数(也就是整个树的度),判断一下度数不为0的节点中是否存在不等于最大度数的。
对于前序遍历,甚至都不需要对每个节点的子节点进行排序(题中说兄弟结点按编号升序访问),因为在按照题意建树后,每个节点的子节点已经是严格递增的了。故直接 \(dfs\) 一下输出结果即可。
#include<bits/stdc++.h>
using namespace std;
#define ios ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
const int N = 1e5 + 10;
vector<int> e[N], ans;
int n, rt, mx, d[N];
void dfs(int u){
ans.emplace_back(u);
for(auto &v : e[u]) dfs(v);
}
int main(){
ios;
cin >> n;
for(int v = 1, u; v <= n; v ++ ){
cin >> u;
if(!u) rt = v;
else e[u].emplace_back(v), d[u] ++ ;
mx = max(mx, d[u]);
}
bool flag = true;
for(int i = 1; i <= n; i ++ )
if(d[i] && d[i] != mx){
flag = false;
break;
}
cout << mx << ' ' << (flag ? "yes" : "no") << "\n";
dfs(rt);
for(int i = 0; i < n; i ++ ) cout << ans[i] << " \n"[i == n - 1];
return 0;
}
L2-4 吉利矩阵
题目描述
所有元素为非负整数,且各行各列的元素和都等于 \(7\) 的 \(3\times 3\) 方阵称为“吉利矩阵”,因为这样的矩阵一共有 \(666\) 种。
本题就请你统计一下,把 \(7\) 换成任何一个 \([2,9]\) 区间内的正整数 \(L\),把矩阵阶数换成任何一个 \([2,4]\) 区间内的正整数 \(N\),满足条件“所有元素为非负整数,且各行各列的元素和都等于 \(L\)”的 \(N\times N\) 方阵一共有多少种?
输入格式
输入在一行中给出 2 个正整数 \(L\) 和 \(N\),意义如题面所述。数字间以空格分隔。
输出格式
在一行中输出满足题目要求条件的方阵的个数。
测试数据
输入样例
7 3
输出样例
666
解题思路
赛时没拿全分(我是不会告诉你我赛场上 \(dfs\) 写不对然后直接写了个 \(16\) 层循环然后每层加上一个提前判断的)。
后来想想其实也是一个很典的搜索 + 剪枝,可以用 \(row[i]\) 和 \(col[i]\) 累加每行/列的和,直接回溯法。
第一个剪枝:在枚举值的时候限制一下行/列的和。
第二个剪枝:在枚举值的时候判断一下每行/列剩下的未取值的位置都取 \(L\) 是否可以到达 \(L\)。
\(dfs\) 从 \((1, 1)\) 开始,假设当前的点为 \((x, y)\),如果递归到的 \((x, y)\) 中 \(y\) 已经大于 \(N\) 了,此时需要进行换行,也就是 \(x = x + 1, y = 1\) 。
当到了最后一行且 \(y > N\) 时,此时整个方阵都搜索完,判断一下是否满足要求,累计答案并停止搜索即可。
#include<bits/stdc++.h>
using namespace std;
#define ios ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
const int N = 5;
int n, k, ans, row[N], col[N];
void dfs(int x, int y){
if(x == n && y > n){
for(int i = 1; i <= n; i ++ ) if(row[i] != k) return;
for(int i = 1; i <= n; i ++ ) if(col[i] != k) return;
ans ++ ;
return;
}
if(y > n) x ++ , y = 1;
for(int i = 0; row[x] + i <= k && col[y] + i <= k; i ++ ){ // 剪枝1 行/列和不超过k
if(row[x] + i + k * (n - y) < k || col[y] + i + k * (n - x) < k) continue; // 剪枝2 可以达到k
row[x] += i;
col[y] += i;
dfs(x, y + 1);
row[x] -= i;
col[y] -= i;
}
}
int main(){
ios;
cin >> k >> n;
dfs(1, 1);
cout << ans << "\n";
return 0;
}
L3-1 夺宝大赛
题目描述
夺宝大赛的地图是一个由 \(n\times m\) 个方格子组成的长方形,主办方在地图上标明了所有障碍、以及大本营宝藏的位置。参赛的队伍一开始被随机投放在地图的各个方格里,同时开始向大本营进发。
所有参赛队从一个方格移动到另一个无障碍的相邻方格(“相邻”是指两个方格有一条公共边)所花的时间都是 1 个单位时间。但当有多支队伍同时进入大本营时,必将发生火拼,造成参与火拼的所有队伍无法继续比赛。大赛规定:最先到达大本营并能活着夺宝的队伍获得胜利。
假设所有队伍都将以最快速度冲向大本营,请你判断哪个队伍将获得最后的胜利。
输入格式
输入首先在第一行给出两个正整数 \(m\) 和 \(n\)(\(2<m,n\le 100\)),随后 \(m\) 行,每行给出 \(n\) 个数字,表示地图上对应方格的状态:\(1\) 表示方格可通过;\(0\) 表示该方格有障碍物,不可通行;\(2\) 表示该方格是大本营。题目保证只有 1 个大本营。
接下来是参赛队伍信息。首先在一行中给出正整数 \(k\)(\(0<k< \frac{m\times n}{2}\)),随后 \(k\) 行,第 \(i\)(\(1\le i\le k\))行给出编号为 \(i\) 的参赛队的初始落脚点的坐标,格式为 x y
。这里规定地图左上角坐标为 1 1
,右下角坐标为 n m
,其中 n
为列数,m
为行数。注意参赛队只能在地图范围内移动,不得走出地图。题目保证没有参赛队一开始就落在有障碍的方格里。
输出格式
在一行中输出获胜的队伍编号和其到达大本营所用的单位时间数量,数字间以 1 个空格分隔,行首尾不得有多余空格。若没有队伍能获胜,则在一行中输出 No winner
.
测试数据
输入样例 1
5 7
1 1 1 1 1 0 1
1 1 1 1 1 0 0
1 1 0 2 1 1 1
1 1 0 0 1 1 1
1 1 1 1 1 1 1
7
1 5
7 1
1 1
5 5
3 1
3 5
1 4
输出样例 1
7 6
样例 1 说明
七支队伍到达大本营的时间顺次为:7、不可能、5、3、3、5、6,其中队伍 4 和 5 火拼了,队伍 3 和 6 火拼了,队伍 7 比队伍 1 早到,所以获胜。
输入样例 2
5 7
1 1 1 1 1 0 1
1 1 1 1 1 0 0
1 1 0 2 1 1 1
1 1 0 0 1 1 1
1 1 1 1 1 1 1
7
7 5
1 3
7 1
1 1
5 5
3 1
3 5
输出样例 2
No winner.
解题思路
由题意可知,只需要求出到达终点时 没有其他队伍到达 并且 距离最小 的队伍编号即可。如果没有满足条件的队伍,输出 No winner
。
很明显用 \(bfs\) 求最短距离即可。
如果每个队伍都进行一次 \(bfs\) 计算到达终点的最短距离,显然会超时。因此,可以反过来 \(bfs\) ,将大本营作为起点进行 \(bfs\),计算到其他各个队伍所在位置的最短距离。
但是!这个题有个很恶心的地方,就是 “这里规定地图左上角坐标为 1 1,右下角坐标为 n m,其中 n 为列数,m 为行数。”
相当于这个地图是竖着的。所给的队伍的坐标也是竖着的地图对于的坐标(真的很反人类,赛时我一直接异或了,样例一直不过)。
故在读入每个队伍坐标后,\(x\) 和 \(y\) 反着存一下即可。其他就没什么了。
#include<bits/stdc++.h>
using namespace std;
#define ios ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
typedef pair<int, int> pii;
const int N = 110, M = 5010;
const int dx[4] = {1, -1, 0, 0}, dy[4] = {0, 0, 1, -1};
int n, m, q, sx, sy, g[N][N], d[N][N];
pii des[M];
void bfs(){
queue<pii> q;
q.emplace(sx, sy);
d[sx][sy] = 0;
while(q.size()){
auto [x, y] = q.front();
q.pop();
for(int k = 0; k < 4; k ++ ){
int nx = x + dx[k], ny = y + dy[k];
if(nx < 1 || ny < 1 || nx > n || ny > m) continue;
if(!g[nx][ny] || ~d[nx][ny]) continue;
q.emplace(nx, ny);
d[nx][ny] = d[x][y] + 1;
}
}
}
int main(){
ios;
cin >> n >> m;
memset(d, -1, sizeof d);
for(int i = 1; i <= n; i ++ )
for(int j = 1; j <= m; j ++ ){
cin >> g[i][j];
if(g[i][j] == 2) sx = i, sy = j;
}
cin >> q;
for(int i = 1, x, y; i <= q; i ++ ) cin >> x >> y, des[i] = {y, x};
bfs();
unordered_map<int, int> cnt; // 每个距离有多少个队伍
for(int i = 1; i <= q; i ++ ){
auto [x, y] = des[i];
cnt[d[x][y]] ++ ;
}
int mn = 1e9, ans = -1;
for(int i = 1; i <= q; i ++ ){
auto [x, y] = des[i];
if(d[x][y] == -1 || cnt[d[x][y]] > 1) continue;
if(d[x][y] < mn) mn = d[x][y], ans = i;
}
if(ans == -1) cout << "No winner." << "\n";
else cout << ans << ' ' << mn << "\n";
return 0;
}
L3-2 工业园区建设
待更新
L3-3 攀岩
待更新
一切都是命运石之门的选择,本文章来源于博客园,作者:MarisaMagic,出处:https://www.cnblogs.com/MarisaMagic/p/18156303,未经允许严禁转载