前言

题解
2025年睿抗机器人开发者大赛CAIP-编程技能赛-高职组(国赛)
感觉这场比赛出题比较典,题意相对清晰(没有阅读理解攻击),整体比较友好。
RC-v1 泼天的富贵
分值: 20分
思路: 分组循环 + 固定长度的滑窗
分组循环保证,至少 k 个元素满足严格递增
然后滑窗,更新最大的结果。
cpp
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int n, d, k;
cin >> n >> d >> k;
int ans = 0;
vector<int> cans;
for (int i = 0; i < n; i++) {
vector<int> arr(d);
for (int j = 0; j < d; j++) {
cin >> arr[j];
}
int tmp_ans = 0;
int s = 0;
while (s + k <= d) {
int e = s + 1;
while (e < d && arr[e - 1] < arr[e]) {
e++;
}
if (e - s >= k) {
// e - k, e
int tmp = 0;
for (int a = s + 1; a < e; a++) {
tmp += arr[a] - arr[a - 1];
if (a - s >= k) {
tmp -= arr[a - k + 1] - arr[a - k];
}
tmp_ans = max(tmp_ans, tmp);
}
}
s = e;
}
if (ans < tmp_ans) {
ans = tmp_ans;
cans = {i + 1};
} else if (ans == tmp_ans) {
cans.push_back(i + 1);
}
}
cout << ans << "/" << k << "\n";
for (int i = 0; i < cans.size(); i++) {
cout << cans[i] << " \n"[i == cans.size() - 1];
}
return 0;
}
RC-v2 行李运输
分值: 25分
思路:二分 + 贪心 check
这题有明显的二阶段性,可以二分承重量,然后贪心构造,最要验证需要的行李数是否小于 k;
cpp
#include <bits/stdc++.h>
using namespace std;
int main() {
int n, k;
cin >> n >> k;
vector<int> arr(n);
for (int &x: arr) {
cin >> x;
}
function<bool(int)> check = [arr, n, k](int m) {
int cnt = 0;
int s = 0;
while (s < n) {
int x = s;
int tmp = 0;
while (s < n && tmp + arr[s] <= m) {
tmp += arr[s];
s++;
}
cnt++;
if (x == s) {
return false;
}
}
return cnt <= k;
};
int l = 1, r = 1 << 30;
while (l <= r) {
int m = l + (r - l) / 2;
if (check(m)) {
r = m - 1;
} else {
l = m + 1;
}
}
cout << l << "\n";
return 0;
}
RC-v3 自然倍树
分值:25 分
思路:分治构造
这题的主框架,就是从后序遍历+中序遍历,反解构造出二叉树。
然后验证该二叉树,是否满足自然倍数特性。
而这两个步骤,其实可以合并,这样就省略了树结构的维护。
cpp
#include <bits/stdc++.h>
using namespace std;
// 分治
bool dfs(vector<int> &arr, int s1, int e1,
vector<int> &brr, int s2, int e2, int d) {
if (s1 > e1) {
return true;
}
// 验证
if (arr[e1] % d != 0) {
return false;
}
// 获得中序遍历的根
int pos = -1;
for (int i = s2; i <= e2; i++) {
if (brr[i] == arr[e1]) {
pos = i;
break;
}
}
int x = pos - s2;
if (!dfs(arr, s1, s1 + x - 1, brr, s2, pos - 1, d + 1)) {
return false;
}
if (!dfs(arr, s1 + x, e1 - 1, brr, pos + 1, e2, d + 1)) {
return false;
}
return true;
}
int main() {
int t;
cin >> t;
while (t-- > 0) {
int n;
cin >> n;
vector<int> arr(n), brr(n);
for (int &x: arr) cin >> x;
for (int &x: brr) cin >> x;
if (dfs(arr, 0, n - 1, brr, 0, n - 1, 1)) {
cout << 1 << "\n";
} else {
cout << 0 << "\n";
}
}
return 0;
}
RC-v4 留下买路财
分值:30分
思路:带状态的dijkstra
这里的边权有点特殊,当属于"霸主"的时候,可以理解为负边权。
那是否会形成负圈呢?不会,题目明确一条边只能使用一次。
可以根据这个负边最多使用一次,引入带状态的dijkstra来求解。
定义 dp[s][n],s∈(0,1){dp[s][n], s\in{(0, 1)}}dp[s][n],s∈(0,1), 0表示没使用过负边,1 表示使用过负边。
cpp
#include <bits/stdc++.h>
using namespace std;
struct E {
int u, w, s;
E(int u, int w, int s)
: u(u), w(w), s(s) {}
bool operator<(const E&rhs) const {
return w > rhs.w;
}
};
int main() {
int n, m, k;
cin >> n >> m >> k;
vector<vector<array<int, 2>>> g(n);
for (int i = 0; i < m; i++) {
int u, v, w;
cin >> u >> v >> w;
u--; v--;
g[u].push_back({v, w});
g[v].push_back({u, w});
}
int inf = 0x3f3f3f3f;
for (int i = 0; i < k; i++) {
int u, v, a, b;
cin >> u >> v >> a >> b;
u--; v--; a--; b--;
// 带状态的 dijkstra
vector<int> dp[2];
dp[0].resize(n, inf);
dp[1].resize(n, inf);
priority_queue<E> pq;
dp[0][a] = 0;
pq.push(E(a, 0, 0));
while (!pq.empty()) {
E e = pq.top(); pq.pop();
if (e.w > dp[e.s][e.u]) continue;
for (auto r: g[e.u]) {
int nv = r[0], w = r[1];
// 判断是否为负边
bool f = (e.u == u && nv == v) || (e.u == v && nv == u);
if (e.s == 0) {
if (f) {
if (dp[1][nv] > e.w - w) {
dp[1][nv] = e.w - w;
pq.push(E(nv, dp[1][nv], 1));
}
} else {
if (dp[0][nv] > e.w + w) {
dp[0][nv] = e.w + w;
pq.push(E(nv, dp[0][nv], 0));
}
}
} else {
if (dp[1][nv] > e.w + w) {
dp[1][nv] = e.w + w;
pq.push(E(nv, dp[1][nv], 1));
}
}
}
}
int z = min(dp[1][b], dp[0][b]);
if (z < 0) {
cout << 0 << "\n";
} else if (z == inf) {
cout << "@_@\n";
} else {
cout << z << "\n";
}
}
return 0;
}
写在最后

