当前位置:首页 > 工具 > 正文内容

AtCoder Beginner Contest 331 算法解析与 C++ 实现

访客 工具 2026年7月6日 1

A - Tomorrow

本题要求计算给定日期的下一天。输入包含一年的月数 M、一个月的天数 D,以及当前的年、月、日。只需对日进行加一操作,并根据 DM 处理进位逻辑即可。

#include <iostream>

void solve() {
    int months_in_year, days_in_month;
    int year, month, day;
    std::cin >> months_in_year >> days_in_month >> year >> month >> day;
    
    day++;
    if (day > days_in_month) {
        day = 1;
        month++;
        if (month > months_in_year) {
            month = 1;
            year++;
        }
    }
    std::cout << year << " " << month << " " << day << "\n";
}

int main() {
    std::ios_base::sync_with_stdio(false);
    std::cin.tie(NULL);
    solve();
    return 0;
}

B - Buy One Carton of Milk

题目提供三种包装的牛奶:6盒装、8盒装和12盒装,价格分别为 abc。目标是购买至少 n 盒牛奶的最小花费。通过枚举6盒装和8盒装的购买数量,可以直接计算出12盒装所需的最小数量,从而将时间复杂度优化至合理范围。

#include <iostream>
#include <algorithm>

void solve() {
    long long n, a, b, c;
    std::cin >> n >> a >> b >> c;
    
    long long min_cost = 1e18;
    for (long long i = 0; i * 6 <= n + 12; ++i) {
        for (long long j = 0; j * 8 <= n + 12; ++j) {
            long long remaining = n - (i * 6 + j * 8);
            long long k = (remaining > 0) ? (remaining + 11) / 12 : 0;
            min_cost = std::min(min_cost, i * a + j * b + k * c);
        }
    }
    std::cout << min_cost << "\n";
}

int main() {
    std::ios_base::sync_with_stdio(false);
    std::cin.tie(NULL);
    solve();
    return 0;
}

C - Sum of Numbers Greater Than Me

对于数组中的每个元素,需要求出严格大于它的其他元素之和。可以通过对数组进行排序并计算前缀和,随后利用二分查找(std::upper_bound)快速定位大于当前元素的起始位置,从而在 $O(1)$ 时间内通过前缀和得出结果。

#include <iostream>
#include <vector>
#include <algorithm>

void solve() {
    int n;
    std::cin >> n;
    std::vector<long long> nums(n), sorted_nums(n);
    for (int i = 0; i < n; ++i) {
        std::cin >> nums[i];
        sorted_nums[i] = nums[i];
    }
    
    std::sort(sorted_nums.begin(), sorted_nums.end());
    std::vector<long long> prefix_sum(n + 1, 0);
    for (int i = 0; i < n; ++i) {
        prefix_sum[i + 1] = prefix_sum[i] + sorted_nums[i];
    }
    
    for (int i = 0; i < n; ++i) {
        auto it = std::upper_bound(sorted_nums.begin(), sorted_nums.end(), nums[i]);
        int idx = std::distance(sorted_nums.begin(), it);
        long long sum_greater = prefix_sum[n] - prefix_sum[idx];
        std::cout << sum_greater << (i == n - 1 ? "" : " ");
    }
    std::cout << "\n";
}

int main() {
    std::ios_base::sync_with_stdio(false);
    std::cin.tie(NULL);
    solve();
    return 0;
}

D - Tile Pattern

给定一个 $N \times N$ 的字符矩阵,将其在二维平面上无限平铺。需要回答多个查询,计算指定子矩阵中字符 'B' 的数量。 核心思路是利用二维前缀和结合周期性数学计算。定义函数 count_B(r, c) 计算从 $(1, 1)$ 到 $(r, c)$ 的 'B' 数量,将其拆分为完整周期块和边缘剩余部分。查询时利用容斥原理 count_B(c, d) - count_B(a-1, d) - count_B(c, b-1) + count_B(a-1, b-1) 即可快速得出答案。

#include <iostream>
#include <vector>
#include <string>

int n, q;
std::vector<std::vector<int>> P;

long long count_B(long long r, long long c) {
    if (r <= 0 || c <= 0) return 0;
    long long full_r = r / n;
    long long rem_r = r % n;
    long long full_c = c / n;
    long long rem_c = c % n;
    
    long long res = 0;
    res += full_r * full_c * P[n][n];
    res += full_r * P[n][rem_c];
    res += full_c * P[rem_r][n];
    res += P[rem_r][rem_c];
    return res;
}

void solve() {
    std::cin >> n >> q;
    P.assign(n + 1, std::vector<int>(n + 1, 0));
    for (int i = 1; i <= n; ++i) {
        std::string row;
        std::cin >> row;
        for (int j = 1; j <= n; ++j) {
            P[i][j] = (row[j - 1] == 'B') ? 1 : 0;
        }
    }
    
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= n; ++j) {
            P[i][j] += P[i - 1][j] + P[i][j - 1] - P[i - 1][j - 1];
        }
    }
    
    while (q--) {
        long long a, b, c, d;
        std::cin >> a >> b >> c >> d;
        a++; b++; c++; d++; // Convert 0-indexed input to 1-indexed
        long long ans = count_B(c, d) - count_B(a - 1, d) - count_B(c, b - 1) + count_B(a - 1, b - 1);
        std::cout << ans << "\n";
    }
}

int main() {
    std::ios_base::sync_with_stdio(false);
    std::cin.tie(NULL);
    solve();
    return 0;
}

E - Set Meal

从数组 $A$ 和 $B$ 中各选一个元素,使得它们的和最大,且选出的下标对 $(i, j)$ 不在给定的禁止列表中。 将两个数组分别排序后,最大的和必然出现在数组末尾。使用最大堆(优先队列)存储候选状态,每次弹出和最大的状态。如果该状态对应的原始下标对合法,则直接输出结果;否则,将其相邻的次大状态(即 $A$ 的下标减一或 $B$ 的下标减一)加入堆中,并使用哈希集合记录已访问状态以避免重复计算。

#include <iostream>
#include <vector>
#include <queue>
#include <set>
#include <algorithm>

struct State {
    int idx_a, idx_b;
    long long sum;
    bool operator<(const State& other) const {
        return sum < other.sum;
    }
};

void solve() {
    int n, m, l;
    std::cin >> n >> m >> l;
    
    std::vector<std::pair<long long, int>> A(n), B(m);
    for (int i = 0; i < n; ++i) {
        std::cin >> A[i].first;
        A[i].second = i;
    }
    for (int i = 0; i < m; ++i) {
        std::cin >> B[i].first;
        B[i].second = i;
    }
    
    std::vector<std::set<int>> invalid_pairs(n);
    for (int i = 0; i < l; ++i) {
        int u, v;
        std::cin >> u >> v;
        u--; v--; 
        invalid_pairs[u].insert(v);
    }
    
    std::sort(A.begin(), A.end());
    std::sort(B.begin(), B.end());
    
    std::priority_queue<State> pq;
    std::set<std::pair<int, int>> visited;
    
    pq.push({n - 1, m - 1, A[n - 1].first + B[m - 1].first});
    visited.insert({n - 1, m - 1});
    
    while (!pq.empty()) {
        State curr = pq.top();
        pq.pop();
        
        int orig_a = A[curr.idx_a].second;
        int orig_b = B[curr.idx_b].second;
        
        if (invalid_pairs[orig_a].find(orig_b) == invalid_pairs[orig_a].end()) {
            std::cout << curr.sum << "\n";
            return;
        }
        
        if (curr.idx_a > 0 && !visited.count({curr.idx_a - 1, curr.idx_b})) {
            visited.insert({curr.idx_a - 1, curr.idx_b});
            pq.push({curr.idx_a - 1, curr.idx_b, A[curr.idx_a - 1].first + B[curr.idx_b].first});
        }
        if (curr.idx_b > 0 && !visited.count({curr.idx_a, curr.idx_b - 1})) {
            visited.insert({curr.idx_a, curr.idx_b - 1});
            pq.push({curr.idx_a, curr.idx_b - 1, A[curr.idx_a].first + B[curr.idx_b - 1].first});
        }
    }
}

int main() {
    std::ios_base::sync_with_stdio(false);
    std::cin.tie(NULL);
    solve();
    return 0;
}

F - Palindrome Query

本题要求支持单点字符修改以及区间回文串查询。由于涉及区间查询和动态修改,线段树是理想的数据结构。 为了高效判断回文,可以在线段树的每个节点中维护该区间的字符串哈希值,包括正序哈希值和逆序哈希值。在合并左右子节点时,正序哈希值的计算方式为:left.hash_forward * base^{right.length} + right.hash_forward;逆序哈希值的计算方式为:right.hash_reverse * base^{left.length} + left.hash_reverse。 在执行区间查询时,提取目标区间的正序和逆序哈希值进行比对。若两者相等,则该区间为回文串。为了有效降低哈希碰撞的概率,建议采用双哈希策略(即使用两组不同的底数和模数)来增强算法的鲁棒性。

标签: AtCoder

相关文章

Trojan服务器搭建与配置

一、整体架构(先对齐认知)Clash Meta (PC / iOS / Android)        ↓ TLS   Trojan Server (443)        ↓     InternetTrojan 的核心是: TLS + HTTPS 流量伪装 看起来像正常网站 非常适合...

Tailscale 的详细用法

Tailscale 是一种基于 WireGuard 协议 的 零配置 VPN(虚拟私有网络)服务,让设备之间能够 安全、加密地直接连接,就像它们在同一个本地网络一样。它的核心特点是 简单、安全、跨平台。Tailscale 非常适合 没有公网 IP、两台电脑不在同一局域网 的场景。 简单来说,Tailscale 是什么?Tailscale 是一款让你的各种设备(电脑、服务器、手机...

Clash Tun 模式 导致 爱快(iKuai SD-Wan)内网域名无法访问

一、Clash  DNS 配置dns:  enable: true  listen: 0.0.0.0:53  ipv6: true  enhanced-mode: redir-host  nameserver:    - 223.5.5.5    - 223.6.6.6iKuai 内网域名 ...

深入解析Node.js运行环境与异步I/O架构

深入解析Node.js运行环境与异步I/O架构

核心定义与价值Node.js本质上是一个JavaScript运行环境,而非编程语言或应用框架。它赋予了JavaScript脱离浏览器在服务端、命令行工具及网络应用中执行的能力。其核心意义在于:用单一语言打通前后端开发壁垒。基于事件驱动与非阻塞I/O的架构特性,Node.js在处理API网关、实时通信及微服务等I/O密集型场景时表现卓越,已成为现代后端工程的主流选择。浏览器沙箱限制1995年Java...

ADO.NET SQL参数化查询的最佳实践

在 ADO.NET 中执行 SQL 查询时,参数化查询是一种关键的安全措施和性能优化手段。它通过将 SQL 命令和用户提供的数据分开处理,有效防止了 SQL 注入攻击,并有助于数据库缓存执行计划。下面总结了几种常用的参数化查询方式。 1. 使用 SqlParameter 对象(推荐) 这是最推荐的参数化查询方式。通过显式创建 SqlParameter 对象,您可以精确控制参数的类...

基于ELK的日志集中化分析系统搭建

构建统一日志管理平台的必要性 在分布式架构中,各服务节点独立运行,日志分散存储于不同主机。传统通过命令行工具如grep、awk逐个检索日志的方式,在数据量庞大时效率极低,难以实现快速定位问题。为提升运维效率,需建立集中式日志处理体系,具备日志采集、传输、存储、分析与告警能力。 ELK技术栈核心组件解析 Elasticsearch:分布式搜索引擎,支持全文检索、实时数据分析和高可用集群部署,...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。