比赛速览
● A - Isosceles
● B - Perfect
● C - New Skill Acquired
● D - 2x2 Erasing 2
● E - Cut in Half
● F - Adding Chords
● G - Swap and Maximize
A - Isosceles
给定三角形三边长度,判断是否为等腰三角形。
检查是否存在两边长度相等。如果 a=b 或 b=c 或 a=c,则三角形是等腰三角形。
对应课程知识点
本题的条件判断对应极客程 《算法A-枚举与算法基础》 课程中的"枚举法"章节,涉及基础的条件分支处理。
参考代码
cpp
#include <bits/stdc++.h>
using namespace std;
int main() {
int a, b, c;
cin >> a >> b >> c;
if (a == b || b == c || a == c) {
cout << "Yes" << endl;
} else {
cout << "No" << endl;
}
return 0;
}
B - Perfect
模拟编程竞赛事件,输出解答出所有题目的选手编号,按完成时间排序。
记录每个选手解答的题目数量,一旦某选手解答的题目数量等于 M 则记录该选手和当前事件索引。最后按记录的事件索引排序输出选手编号。
对应课程知识点
本题的模拟与数据结构应用对应极客程 《算法A-枚举与算法基础》 课程中的"结构体"章节,涉及事件处理技术。
参考代码
cpp
#include <bits/stdc++.h>
using namespace std;
int main() {
int N, M, K;
cin >> N >> M >> K;
vector<int> count(N + 1, 0);
vector<pair<int, int>> completions;
for (int i = 1; i <= K; i++) {
int p, q;
cin >> p >> q;
count[p]++;
if (count[p] == M) {
completions.push_back({i, p});
}
}
sort(completions.begin(), completions.end());
for (auto& comp : completions) {
cout << comp.second << endl;
}
return 0;
}
C - New Skill Acquired
根据技能学习规则,计算最终能学会的技能数量。
对于每个 (a_i, b_i, c_i),如果 c_i = 1,则技能 a_i 已学会;否则从 a_i 与 b_i 向 c_i 连边。从所有已学会的技能出发进行 BFS 遍历统计可达节点数。
对应课程知识点
本题的图论遍历对应极客程 《算法C-深搜与宽搜》 课程中的"BFS-图论基础"内容。
参考代码
cpp
#include <bits/stdc++.h>
using namespace std;
int main() {
int N, K;
cin >> N >> K;
vector<vector<int>> graph(N + 1);
vector<bool> learned(N + 1, false);
queue<int> q;
for (int i = 0; i < K; i++) {
int a, b, c;
cin >> a >> b >> c;
if (c == 1) {
if (!learned[a]) {
learned[a] = true;
q.push(a);
}
} else {
graph[a].push_back(c);
graph[b].push_back(c);
}
}
int count = 0;
while (!q.empty()) {
int skill = q.front();
q.pop();
count++;
for (int next : graph[skill]) {
if (!learned[next]) {
learned[next] = true;
q.push(next);
}
}
}
cout << count << endl;
return 0;
}
D - 2x2 Erasing 2
通过涂白最少的黑色单元格,使网格中不存在任何 2×2 的全为黑色的子网格。
"最少需要涂白的单元格数量"即为"总黑格数 - 最多能保留的黑格数"。使用状态压缩 DP,dp[i][mask] 表示第 i 行状态为 mask 时最多能保留的黑格数,检查相邻行是否形成 2×2 全黑子网格。
对应课程知识点
本题的状态压缩动态规划对应极客程 《算法D-入门级动态规划》 课程中的"网格类DP"内容。
参考代码
cpp
#include <bits/stdc++.h>
using namespace std;
int main() {
int H, W;
cin >> H >> W;
vector<string> grid(H);
int total_black = 0;
for (int i = 0; i < H; i++) {
cin >> grid[i];
for (int j = 0; j < W; j++) {
if (grid[i][j] == '#') {
total_black++;
}
}
}
vector<int> orig(H, 0);
for (int i = 0; i < H; i++) {
for (int j = 0; j < W; j++) {
if (grid[i][j] == '#') {
orig[i] |= (1 << j);
}
}
}
vector<vector<int>> dp(H, vector<int>(1 << W, -1));
for (int mask = orig[0]; ; mask = (mask - 1) & orig[0]) {
dp[0][mask] = __builtin_popcount(mask);
if (mask == 0) break;
}
for (int i = 1; i < H; i++) {
for (int mask = orig[i]; ; mask = (mask - 1) & orig[i]) {
for (int prev_mask = orig[i-1]; ; prev_mask = (prev_mask - 1) & orig[i-1]) {
if (dp[i-1][prev_mask] == -1) {
if (prev_mask == 0) break;
continue;
}
bool valid = true;
for (int j = 0; j < W - 1; j++) {
bool a = (prev_mask >> j) & 1;
bool b = (prev_mask >> (j + 1)) & 1;
bool c = (mask >> j) & 1;
bool d = (mask >> (j + 1)) & 1;
if (a && b && c && d) {
valid = false;
break;
}
}
if (valid) {
dp[i][mask] = max(dp[i][mask], dp[i-1][prev_mask] + __builtin_popcount(mask));
}
if (prev_mask == 0) break;
}
if (mask == 0) break;
}
}
int max_retained = 0;
for (int mask = 0; mask < (1 << W); mask++) {
max_retained = max(max_retained, dp[H-1][mask]);
}
cout << total_black - max_retained << endl;
return 0;
}
E - Cut in Half
经过 K 次分割最长木棍操作后,求第 L 长的木棍长度。
使用二分答案,对于给定的长度 mid,计算经过 K 次操作后长度不小于 mid 的木棍数量是否不少于 L。对于每个木棍,计算能产生多少根 ≥ mid 的木棍及所需切割次数。
对应课程知识点
本题的二分答案技术对应极客程 《算法B-贪心法与优化》 课程中的"二分答案"章节,涉及函数单调性应用。
参考代码
cpp
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int main() {
int N, K, L;
cin >> N >> K >> L;
vector<ll> A(N);
for (int i = 0; i < N; i++) {
cin >> A[i];
}
double left = 0, right = 1e9;
for (int iter = 0; iter < 100; iter++) {
double mid = (left + right) / 2;
ll total_count = 0;
ll total_cuts = 0;
for (int i = 0; i < N; i++) {
if (A[i] < mid) continue;
ll pieces = min((ll)(A[i] / mid), (ll)K + 1);
ll cuts_needed = pieces - 1;
if (total_cuts + cuts_needed <= K) {
total_count += pieces;
total_cuts += cuts_needed;
} else {
ll remaining_cuts = K - total_cuts;
total_count += remaining_cuts + 1;
total_cuts = K;
}
}
if (total_count >= L) {
left = mid;
} else {
right = mid;
}
}
cout << fixed << setprecision(10) << left << endl;
return 0;
}
F - Adding Chords
在圆周上按顺序添加弦,如果新弦与已有弦相交则不添加,判断每个查询的弦是否被添加。
圆上弦相交的经典结论:两条弦相交当且仅当它们的四个端点在圆周上交错排列。使用线段树维护每个点的匹配点,对于查询检查区间内是否存在已画弦的端点使得新弦与已有弦相交。
对应课程知识点
本题的线段树应用对应极客程 《算法D-入门级动态规划》 后续高级课程内容,涉及高级数据结构的使用。
参考代码
cpp
#include <bits/stdc++.h>
using namespace std;
struct SegmentTree {
int n;
vector<int> min_val, max_val;
SegmentTree(int size) {
n = size;
min_val.assign(4 * n, INT_MAX);
max_val.assign(4 * n, INT_MIN);
}
void update(int idx, int l, int r, int pos, int val) {
if (l == r) {
min_val[idx] = min(min_val[idx], val);
max_val[idx] = max(max_val[idx], val);
return;
}
int mid = (l + r) / 2;
if (pos <= mid) {
update(idx * 2, l, mid, pos, val);
} else {
update(idx * 2 + 1, mid + 1, r, pos, val);
}
min_val[idx] = min(min_val[idx * 2], min_val[idx * 2 + 1]);
max_val[idx] = max(max_val[idx * 2], max_val[idx * 2 + 1]);
}
pair<int, int> query(int idx, int l, int r, int ql, int qr) {
if (ql > qr) return {INT_MAX, INT_MIN};
if (ql <= l && r <= qr) {
return {min_val[idx], max_val[idx]};
}
int mid = (l + r) / 2;
pair<int, int> res = {INT_MAX, INT_MIN};
if (ql <= mid) {
auto left_res = query(idx * 2, l, mid, ql, qr);
res.first = min(res.first, left_res.first);
res.second = max(res.second, left_res.second);
}
if (qr > mid) {
auto right_res = query(idx * 2 + 1, mid + 1, r, ql, qr);
res.first = min(res.first, right_res.first);
res.second = max(res.second, right_res.second);
}
return res;
}
};
int main() {
int N, Q;
cin >> N >> Q;
SegmentTree seg_tree(N + 1);
for (int i = 0; i < Q; i++) {
int a, b;
cin >> a >> b;
if (a > b) swap(a, b);
auto [min_val, max_val] = seg_tree.query(1, 1, N, a + 1, b - 1);
bool intersect = false;
if (min_val < a || max_val > b) {
intersect = true;
}
if (!intersect) {
cout << "Yes" << endl;
seg_tree.update(1, 1, N, a, b);
seg_tree.update(1, 1, N, b, a);
} else {
cout << "No" << endl;
}
}
return 0;
}
G - Swap and Maximize
通过交换相邻元素,最大化数组相邻元素差的绝对值之和。
要最大化相邻元素差的绝对值之和,应将数组重新排列为先递增后递减的"山峰"形状。将数组排序后交替从前后两部分取元素构造新数组,计算两种排列方式的价值取最大值。
对应课程知识点
本题的贪心策略与排序技术对应极客程 《算法B-贪心法与优化》 课程中的"贪心法"章节,涉及最优排列构造。
参考代码
cpp
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int main() {
int N;
cin >> N;
vector<ll> A(N);
for (int i = 0; i < N; i++) {
cin >> A[i];
}
sort(A.begin(), A.end());
vector<ll> arranged;
int left = 0, right = N - 1;
for (int i = 0; i < N; i++) {
if (i % 2 == 0) {
arranged.push_back(A[left++]);
} else {
arranged.push_back(A[right--]);
}
}
ll value = 0;
for (int i = 1; i < N; i++) {
value += abs(arranged[i] - arranged[i - 1]);
}
vector<ll> arranged2;
left = 0, right = N - 1;
for (int i = 0; i < N; i++) {
if (i % 2 == 1) {
arranged2.push_back(A[left++]);
} else {
arranged2.push_back(A[right--]);
}
}
ll value2 = 0;
for (int i = 1; i < N; i++) {
value2 += abs(arranged2[i] - arranged2[i - 1]);
}
cout << max(value, value2) << endl;
return 0;
}