天梯赛练习(L2-001 ~ L2-006)
L2-001 紧急救援
作为一个城市的应急救援队伍的负责人,你有一张特殊的全国地图。在地图上显示有多个分散的城市和一些连接城市的快速道路。每个城市的救援队数量和每一条连接两个城市的快速道路长度都标在地图上。当其他城市有紧急求助电话给你的时候,你的任务是带领你的救援队尽快赶往事发地,同时,一路上召集尽可能多的救援队。
Input
输入第一行给出4个正整数N、M、S、D,其中N(2≤N≤500)是城市的个数,顺便假设城市的编号为0 ~ (N−1);M是快速道路的条数;S是出发地的城市编号;D是目的地的城市编号。
第二行给出N个正整数,其中第i个数是第i个城市的救援队的数目,数字间以空格分隔。随后的M行中,每行给出一条快速道路的信息,分别是:城市1、城市2、快速道路的长度,中间用空格分开,数字均为整数且不超过500。输入保证救援可行且最优解唯一。
Output
第一行输出最短路径的条数和能够召集的最多的救援队数量。第二行输出从S到D的路径中经过的城市编号。数字间以空格分隔,输出结尾不能有多余空格。
思路:明显的单源最短路,但是需要加一点东西:用Dijkstra每次更新时需要更新所经过路径的救援队数量,用last数组记录路径,最后递归输出路径即可。对于相同长度的路径数量,DFS跑一遍记录一下即可。
AC Code:
#include <bits/stdc++.h>typedef long long ll;
#define int long long
typedef std::pair<int, int> PII;
const int N = 1e5 + 5;
int n, m, s, d, ans;
int a[N], last[N];
ll dis[N], cnt[N];
bool vis[N];
std::vector<PII> vec[N];void DFS(int u, ll dist) {if(u == d && dist == dis[d]) ans ++;else {for(auto v : vec[u]) {int j = v.first;if(dis[d] >= dist + v.second)DFS(j, dist + v.second);}}
}void Dijkstra(int u) {std::priority_queue<PII, std::vector<PII>, std::greater<PII> > pq;memset(dis, 0x3f3f3f3f, sizeof(dis));dis[u] = 0;cnt[u] = a[u];pq.push({0, u});while(!pq.empty()) {auto [w, y] = pq.top();pq.pop();if(vis[y]) continue;vis[y] = true;for(auto v : vec[y]) {int to = v.first;if(dis[to] >= dis[y] + v.second) {if(dis[to] > dis[y] + v.second) {last[to] = y;dis[to] = dis[y] + v.second;cnt[to] = cnt[y] + a[to];}else if(dis[to] == dis[y] + v.second) {if(cnt[to] < cnt[y] + a[to]) {cnt[to] = cnt[y] + a[to];last[to] = y;}}}if(!vis[to]) pq.push({dis[to], to});}}
}void print(int u) {if(u != s) {print(last[u]);std::cout << ' ' << u;}elsestd::cout << u;
}signed main() {std::ios::sync_with_stdio(false);std::cin.tie(0);std::cout.tie(0);std::cin >> n >> m >> s >> d;for(int i = 0; i < n; i ++) {std::cin >> a[i];}while(m --) {int u, v, w;std::cin >> u >> v >> w;vec[u].push_back({v, w});vec[v].push_back({u, w});}Dijkstra(s);DFS(s, 0);std::cout << ans << ' ' << cnt[d] << '\\n';print(d);return 0;
}
L2-002 链表去重
给定一个带整数键值的链表 L,你需要把其中绝对值重复的键值结点删掉。即对每个键值 K,只有第一个绝对值等于 K 的结点被保留。同时,所有被删除的结点须被保存在另一个链表上。例如给定 L 为 21→-15→-15→-7→15,你需要输出去重后的链表 21→-15→-7,还有被删除的链表 -15→15。
Input
输入在第一行给出 L 的第一个结点的地址和一个正整数 N(≤10^5,为结点总数)。一个结点的地址是非负的 5 位整数,空地址 NULL 用 −1 来表示。
随后 N 行,每行按以下格式描述一个结点:
地址 键值 下一个结点
其中地址
是该结点的地址,键值
是绝对值不超过104的整数,下一个结点
是下个结点的地址。
Output
首先输出去重后的链表,然后输出被删除的链表。每个结点占一行,按输入的格式输出。
思路:结构体存链表,然后遍历链表去重,注意输出时的节点地址和下一个节点的地址。这种题写的很少,不算熟练。
AC Code:
#include <bits/stdc++.h>typedef long long ll;
const int N = 1e5 + 5;
int st, n;
bool vis[N];
int mp[N];struct node {int add, next, num;
} e[N];int main() {// std::ios::sync_with_stdio(false);// std::cin.tie(0);// std::cout.tie(0);std::cin >> st >> n;for(int i = 1; i <= n; i ++) {int l, val, r;std::cin >> l >> val >> r;e[l] = {l, r, val};}int sst = -1, en;std::vector<node> a, b;for(int i = st; i != -1; i = e[i].next) {if(mp[abs(e[i].num)]) b.push_back(e[i]);elsea.push_back(e[i]);mp[abs(e[i].num)] ++;}for(int i = 0; i < a.size(); i ++) {printf("%05d", a[i].add);std::cout << ' ' << a[i].num << ' '; if(i == a.size() - 1)std::cout << -1 << '\\n';elseprintf("%05d\\n", a[i + 1].add);}for(int i = 0; i < b.size(); i ++) {printf("%05d", b[i].add);std::cout << ' ' << b[i].num << ' '; if(i == b.size() - 1)std::cout << -1 << '\\n';elseprintf("%05d\\n", b[i + 1].add);}return 0;
}
L2-003 月饼
月饼是中国人在中秋佳节时吃的一种传统食品,不同地区有许多不同风味的月饼。现给定所有种类月饼的库存量、总售价、以及市场的最大需求量,请你计算可以获得的最大收益是多少。
注意:销售时允许取出一部分库存。样例给出的情形是这样的:假如我们有 3 种月饼,其库存量分别为 18、15、10 万吨,总售价分别为 75、72、45 亿元。如果市场的最大需求量只有 20 万吨,那么我们最大收益策略应该是卖出全部 15 万吨第 2 种月饼、以及 5 万吨第 3 种月饼,获得 72 + 45/2 = 94.5(亿元)。
Input
每个输入包含一个测试用例。每个测试用例先给出一个不超过 1000 的正整数 N 表示月饼的种类数、以及不超过 500(以万吨为单位)的正整数 D 表示市场最大需求量。随后一行给出 N 个正数表示每种月饼的库存量(以万吨为单位);最后一行给出 N 个正数表示每种月饼的总售价(以亿元为单位)。数字间以空格分隔。
Output
对每组测试用例,在一行中输出最大收益,以亿元为单位并精确到小数点后 2 位。
思路:按照单价排序即可。
AC Code:
#include <bits/stdc++.h>typedef long long ll;
const int N = 1e5 + 5;
int n, d;
double a;struct node {double sum, pr;double d;bool operator < (const node &a) const {return d > a.d;}
} e[N];int main() {std::ios::sync_with_stdio(false);std::cin.tie(0);std::cout.tie(0);std::cin >> n >> d;for(int i = 1; i <= n; i ++) {std::cin >> a;e[i].sum = a;}for(int i = 1; i <= n; i ++) {std::cin >> a;e[i].pr = a;e[i].d = e[i].pr / e[i].sum;}std::sort(e + 1, e + 1 + n);double ans = 0;for(int i = 1; i <= n; i ++) {if(d) {ans += std::min(d, (int)e[i].sum) * e[i].d;d -= std::min(d, (int)e[i].sum);}elsebreak;}std::cout << std::fixed << std::setprecision(2) << ans << '\\n';return 0;
}
L2-004 这是二叉搜索树吗?
一棵二叉搜索树可被递归地定义为具有下列性质的二叉树:对于任一结点,
- 其左子树中所有结点的键值小于该结点的键值;
- 其右子树中所有结点的键值大于等于该结点的键值;
- 其左右子树都是二叉搜索树。
所谓二叉搜索树的“镜像”,即将所有结点的左右子树对换位置后所得到的树。
给定一个整数键值序列,现请你编写程序,判断这是否是对一棵二叉搜索树或其镜像进行前序遍历的结果。
Input
输入的第一行给出正整数 N(≤1000)。随后一行给出 N 个整数键值,其间以空格分隔。
Output
如果输入序列是对一棵二叉搜索树或其镜像进行前序遍历的结果,则首先在一行中输出 YES
,然后在下一行输出该树后序遍历的结果。数字间有 1 个空格,一行的首尾不得有多余空格。若答案是否,则输出 NO
。
思路:看到有的题解是直接建树,整了一堆指针,但不会指针怎么办呢。可以对前序遍历进行一波逆操作,每次查找根节点的两棵子树的范围,如果两棵子树边界差1,则说明是满足条件的子树,否则不满足条件。用vector存一下后序遍历的根节点,vector的大小可以用来判断对于树和其镜像的合理性。注意边界。
AC Code:
#include <bits/stdc++.h>typedef long long ll;
const int N = 2e5 + 5;
int n;
int a[N];
bool flag;
std::vector<int> v;void DFS(int l, int r) {if(l > r) return;int L = l + 1, R = r;if(flag) {while(a[L] < a[l] && L <= r) L ++;while(a[R] >= a[l] && R > l) R --;}else {while(a[L] >= a[l] && L <= r) L ++;while(a[R] < a[l] && R > l) R --;}if(L - R != 1) return;DFS(l + 1, R);DFS(L, r);v.push_back(a[l]);
}int main() {std::ios::sync_with_stdio(false);std::cin.tie(0);std::cout.tie(0);std::cin >> n;for(int i = 1; i <= n; i ++) {std::cin >> a[i];}flag = true;DFS(1, n);if(v.size() != n) {v.clear();flag = false;DFS(1, n);if(v.size() != n) {std::cout << "NO" << '\\n';return 0;}}std::cout << "YES" << '\\n';for(int i = 0; i < n; i ++) {std::cout << v[i] << " \\n"[i == n - 1];}return 0;
}
L2-005 集合相似度
给定两个整数集合,它们的相似度定义为:Nc/Nt×100%。其中Nc是两个集合都有的不相等整数的个数,Nt是两个集合一共有的不相等整数的个数。你的任务就是计算任意一对给定集合的相似度。
Input
输入第一行给出一个正整数N(≤50),是集合的个数。随后N行,每行对应一个集合。每个集合首先给出一个正整数M(≤10^4),是集合中元素的个数;然后跟M个[0,10^9]区间内的整数。
之后一行给出一个正整数K(≤2000),随后K行,每行对应一对需要计算相似度的集合的编号(集合从1到N编号)。数字间以空格分隔。
Output
对每一对需要计算的集合,在一行中输出它们的相似度,为保留小数点后2位的百分比数字。
思路:数据范围很小,直接暴力离线做就可以,注意输入的时候也要去重。
AC Code:
#include <bits/stdc++.h>typedef long long ll;
const int N = 105;
int n, q;
std::vector<int> v[N];
double ans[N][N];int main() {std::ios::sync_with_stdio(false);std::cin.tie(0);std::cout.tie(0);std::cin >> n;std::unordered_map<int, int> mp;for(int i = 1; i <= n; i ++) {int x;std::cin >> x;mp.clear();for(int j = 1; j <= x; j ++) {int y;std::cin >> y;if(mp[y]) continue;v[i].push_back(y);mp[y] ++;}}for(int i = 1; i <= n; i ++) {for(int j = i + 1; j <= n; j ++) {mp.clear();int cntall = 0, cnt = 0;for(int k = 0; k < v[i].size(); k ++) {mp[v[i][k]] ++;}for(int k = 0; k < v[j].size(); k ++) {mp[v[j][k]] ++;}for(auto [x, y] : mp) {if(y > 1) cntall ++;}cnt = mp.size();ans[i][j] = ans[j][i] = cntall * 1.0 / cnt;}}std::cin >> q;while(q --) {int x, y;std::cin >> x >> y;std::cout << std::fixed << std::setprecision(2) << ans[x][y] * 100 << '%' << '\\n';}return 0;
}
L2-006 树的遍历
给定一棵二叉树的后序遍历和中序遍历,请你输出其层序遍历的序列。这里假设键值都是互不相等的正整数。
Input
输入第一行给出一个正整数N(≤30),是二叉树中结点的个数。第二行给出其后序遍历序列。第三行给出其中序遍历序列。数字间以空格分隔。
Output
在一行中输出该树的层序遍历的序列。数字间以1个空格分隔,行首尾不得有多余空格。
思路:又是树上问题。一般就是搜索解决了。按照后序遍历找到每棵子树的根的位置,在中序遍历中其两侧就是它的子树。一遍DFS建树,再用BFS得到层遍历序即可。
AC Code:
#include <bits/stdc++.h>typedef long long ll;
const int N = 105;
int n;
int a[N], b[N];
std::vector<int> ans;struct node {int l, r;
} e[N];int DFS(int l1, int r1, int l2, int r2) {if(l1 > r1) return 0;int root = a[r2], p1 = l1, p2;while(b[p1] != root) p1 ++;p2 = p1 - l1;e[root].l = DFS(l1, p1 - 1, l2, l2 + p2 - 1);e[root].r = DFS(p1 + 1, r1, l2 + p2, r2 - 1);return root;
}void BFS(int u) {std::queue<int> q;q.push(u);while(!q.empty()) {int now = q.front();q.pop();ans.push_back(now);if(e[now].l) q.push(e[now].l);if(e[now].r) q.push(e[now].r);}
}int main() {std::ios::sync_with_stdio(false);std::cin.tie(0);std::cout.tie(0);std::cin >> n;for(int i = 1; i <= n; i ++) {std::cin >> a[i];}for(int i = 1; i <= n; i ++) {std::cin >> b[i];}DFS(1, n, 1, n);BFS(a[n]);for(int i = 0; i < n; i ++) {std::cout << ans[i] << " \\n"[i == n - 1];}return 0;
}