AtCoder Beginner Contest 424 题解

比赛速览

● 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;
}
相关推荐
CS创新实验室3 小时前
深入解析快速排序(Quicksort):从原理到实践
数据结构·算法·排序算法·快速排序
今天又在学代码写BUG口牙3 小时前
MFC应用程序,工作线程学习记录
c++·mfc·1024程序员节
j_xxx404_3 小时前
C++ STL简介:从原理到入门使用指南
开发语言·c++
15Moonlight3 小时前
06-MySQL基础查询
数据库·c++·mysql·1024程序员节
Dream it possible!3 小时前
LeetCode 面试经典 150_链表_反转链表 II(60_92_C++_中等)(头插法)
c++·leetcode·链表·面试
yeshihouhou3 小时前
树 B树和B+树
数据结构
大数据张老师4 小时前
数据结构——B+树的基本概念
数据结构·1024程序员节
草莓工作室4 小时前
数据结构12:二叉树的API及其实现
c语言·数据结构·二叉树
十五年专注C++开发4 小时前
Drogon: 一个开源的C++高性能Web框架
linux·c++·windows·后端开发·服务器开发