当前位置:首页 > 技术 > 正文内容

Codeforces 1234 题目详解与实现策略

访客 技术 2026年7月28日 1

A. 统一价格策略

问题描述:

存在多组测试用例。每组给定 $n$ 个商品及其对应的成本 $a_i$。现在需要设定一个统一的整数售价,使得所有商品售出后总利润非负(总收入 $\ge$ 总成本)。请计算该最低定价。

算法分析

这是一个基础的数学问题。为了保证不亏本,总售价必须大于或等于总成本。假设设定价格为 $P$,则需满足 $P \times n \ge \sum a_i$。因此,$P \ge \frac{\sum a_i}{n}$。由于 $P$ 必须是整数,我们需要对除法结果进行向上取整处理。数学公式可表示为 $\lfloor \frac{\sum a_i + n - 1}{n} \rfloor$。

代码实现

#include <iostream>
#include <numeric>

void solve() {
    int count;
    std::cin >> count;
    long long total_cost = 0;
    for (int i = 0; i < count; ++i) {
        int price;
        std::cin >> price;
        total_cost += price;
    }
    // 向上取整计算:(总和 + 数量 - 1) / 数量
    std::cout << (total_cost + count - 1) / count << "\n";
}

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    int queries;
    std::cin >> queries;
    while (queries--) {
        solve();
    }
    return 0;
}

B. 社交网络消息队列

问题描述:

手机屏幕显示上限为 $k$ 条信息。按顺序接收 $n$ 条来自不同发件人 ID 的消息。显示逻辑如下:

  • 若发件人已显示,保持不动。
  • 若未显示且当前未满,置顶插入,其余下移。
  • 若未显示且已满,移除最底下一条,再执行上一条操作。
初始屏幕为空,求最终屏幕上保留的发件人列表(从上到下)。

算法分析

此题模拟了一个具有容量限制的"最近使用优先"机制。核心在于维护一个有序集合,新元素总是进入头部,满时尾部出队。可以使用双向链表或者配合哈希集合使用的向量来模拟这一过程。为了快速判断元素是否存在,需要一个 $O(1)$ 查找的数据结构。

代码实现

#include <iostream>
#include <vector>
#include <unordered_set>

using namespace std;

int main() {
    ios::sync_with_stdio(0); cin.tie(0);
    int msg_cnt, limit;
    cin >> msg_cnt >> limit;
    
    vector<int> screen;
    unordered_set<int> visible;
    
    for (int i = 0; i < msg_cnt; ++i) {
        int sender_id;
        cin >> sender_id;
        
        if (visible.count(sender_id)) continue;
        
        visible.insert(sender_id);
        screen.push_front(sender_id);
        
        if ((int)screen.size() > limit) {
            int removed = screen.back();
            screen.pop_back();
            visible.erase(removed);
        }
    }
    
    cout << screen.size() << "\n";
    for (const auto& id : screen) {
        cout << id << " ";
    }
    cout << endl;
    return 0;
}

C. 水管连通性判定

问题描述:

有一个 $2 \times n$ 的网格,每个格子放置一种特定形状的水管。目标是调整部分水管的角度,使水流从第 1 行左侧进入,并从第 2 行右侧流出。给出的示例图示了两种关键状态:
水管类型 成功连通的样例如图:
连通样例

算法分析

观察水管类型可以发现,水平直管(类型 1、2)用于维持行内流动,而弯头管(类型 3~6)用于切换行。为了使水能从左上流向右下,路径是固定的:一旦遇到弯头,必须换行;遇到直管则继续同行。类型 2 的水管本质上是垂直堵死的,必须旋转成类型 1 才能通行。如果某一步水流方向要求与实际管路冲突(例如在需要横向移动的位置遇到了无法横向导流的管子),则方案不可行。

代码实现

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

using namespace std;

void solve() {
    int n;
    cin >> n;
    vector<string> grid(2);
    cin >> grid[0] >> grid[1];
    
    int current_row = 1; // 0-indexed internally
    
    for (int col = 0; col < n; ++col) {
        char c1 = grid[0][col] - '0';
        char c2 = grid[1][col] - '0';
        
        if (c1 > 2) c1 = 3; 
        if (c2 > 2) c2 = 3;
        
        // 简化逻辑:类型为 1 代表直通,类型 3 代表转向
        bool straight_0 = (c1 == 1);
        bool straight_1 = (c2 == 1);
        
        if (current_row == 0) {
            if (!straight_0) {
                if (!straight_1) { // 两行都需要转向,但这里只需要一行通
                    cout << "NO\n";
                    return;
                }
                current_row = 1;
            }
        } else {
            if (!straight_1) {
                if (!straight_0) {
                    cout << "NO\n";
                    return;
                }
                current_row = 0;
            }
        }
    }
    
    if (current_row == 1) cout << "YES\n";
    else cout << "NO\n";
}

int main() {
    ios::sync_with_stdio(0); cin.tie(0);
    int t;
    cin >> t;
    while(t--) solve();
    return 0;
}

D. 区间不同字符查询

问题描述:

维护一个仅包含小写字母的字符串,支持单点修改和区间查询(统计区间内不同字符的数量)。操作次数较多,需高效处理。

算法分析

由于字符集较小(26 个小写字母),可以针对每种字符分别建立数据结构(如树状数组或线段树),记录其出现位置的前缀和。查询区间 $[L, R]$ 时,遍历 26 种字符,检查其在区间内的计数是否大于 0。单次查询复杂度为 $O(26 \log n)$,总体效率足够。

代码实现

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

using namespace std;

const int MAXN = 100005;
struct FenwickTree {
    vector<int> tree;
    int n;
    FenwickTree(int size) : n(size), tree(size + 1, 0) {}
    void update(int idx, int val) {
        for (; idx <= n; idx += idx & -idx)
            tree[idx] += val;
    }
    int query(int idx) {
        int res = 0;
        for (; idx > 0; idx -= idx & -idx)
            res += tree[idx];
        return res;
    }
    int range_query(int l, int r) {
        return query(r) - query(l - 1);
    }
};

int main() {
    ios::sync_with_stdio(0); cin.tie(0);
    string str;
    cin >> str;
    int n = str.length();
    // 创建 26 个树状数组,分别管理 a-z
    vector<FenwickTree> trees(26, FenwickTree(n));
    
    for (int i = 0; i < n; ++i) {
        int char_idx = str[i] - 'a';
        trees[char_idx].update(i + 1, 1);
    }
    
    int m;
    cin >> m;
    vector<int> arr(n);
    for(int i=0; i<n; ++i) arr[i] = str[i] - 'a';

    while (m--) {
        int type, x, y;
        char c;
        cin >> type >> x;
        --x; // 转 0-index
        
        if (type == 1) {
            cin >> c;
            // 移除旧字符,添加新字符
            trees[arr[x]].update(x + 1, -1);
            arr[x] = c - 'a';
            trees[arr[x]].update(x + 1, 1);
        } else {
            cin >> y;
            int distinct_count = 0;
            for (int k = 0; k < 26; ++k) {
                if (trees[k].range_query(x + 1, y) > 0) {
                    distinct_count++;
                }
            }
            cout << distinct_count << "\n";
        }
    }
    return 0;
}

E. 特殊排列的距离代价

问题描述:

定义排列 $p_i(n)$ 为将数字 $i$ 置于首位,其余数字保持相对升序的排列。给定序列 $x$,定义 $f(p) = \sum |pos(x_j) - pos(x_{j+1})|$。需输出所有 $i \in [1, n]$ 对应的 $f(p_i(n))$。

算法分析

直接暴力计算每个排列代价会超时。观察发现,当 $i$ 变为 $i+1$ 时,实际上只有数值 $i$ 和 $i+1$ 的位置发生了显著变化(交换了某种意义上的前后关系),其他元素相对位置不变。我们可以先计算 $p_1$ 的基础代价,然后维护当前的总距离,在每次变换时,只减去涉及 $i$ 和 $i+1$ 的旧距离贡献,更新它们的位置函数值后,加上新距离贡献。由于每次更新仅与 $x$ 中相邻项相关,预处理 $x$ 的边并统计频次可在均摊 $O(N+M)$ 时间内完成。

代码实现

#include <iostream>
#include <vector>
#include <cmath>

using namespace std;

// 计算值 v 在排列 p_i 中的位置
int getPosition(int v, int pivot, int n) {
    if (v == pivot) return 1;
    if (v < pivot) return v + 1;
    return v;
}

int main() {
    ios::sync_with_stdio(0); cin.tie(0);
    int n, m;
    cin >> n >> m;
    vector<int> x(m);
    for (int i = 0; i < m; ++i) cin >> x[i];
    
    // 初始计算 p_1 的总代价 (pivot = 1)
    long long current_ans = 0;
    // 邻接表优化:记录哪些值在 x 中是相邻的
    vector<vector<int>> adj(n + 1);
    for (int i = 0; i < m - 1; ++i) {
        int u = x[i], v = x[i + 1];
        adj[u].push_back(v);
        if (u != v) adj[v].push_back(u); 
        // 注意:这里为了 O(1) 更新,实际逻辑更倾向于直接遍历时检查
    }
    
    // 基础计算
    for (int i = 0; i < m - 1; ++i) {
        int u = x[i], v = x[i+1];
        current_ans += abs(getPosition(u, 1, n) - getPosition(v, 1, n));
    }
    
    vector<long long> results(n + 1);
    results[1] = current_ans;
    
    // 递推计算 p_2 到 p_n
    // 实际上对于每个 i,我们考虑将 pivot 从 i 移到 i+1 的变化
    // 这里的逻辑简化为:每次改变 pivot 时,只有涉及当前 pivot 值的距离段会变
    // 但考虑到 pivot 从 i 变 i+1,位置函数变了,对所有涉及的边都要重新评估?
    // 最优解法是:直接利用差分性质,或者因为 N,M 较大,采用前缀和技巧。
    // 鉴于篇幅,此处展示基于增量修正的核心逻辑框架:
    
    // 重新构建逻辑:
    // 对于每一对 (x[j], x[j+1]),它对 f(p_k) 的贡献只取决于 k 是否小于、等于或大于其中的数。
    // 我们可以统计每类关系出现的次数,然后线性扫描 k。
    
    // 为了代码简洁且符合题意重写要求,以下提供基于预处理的实现思路:
    // 初始化所有边的差值贡献
    vector<long long> ans_list(n + 1, 0);
    
    // 这种方法可能过于复杂,回到提示的"交换"思想:
    // 每次迭代,我们只关心涉及数字 i 的边。
    
    // 下面是一个可行的 O(M+N) 实现框架:
    vector<long long> final_res(n + 1);
    long long total_dist = 0;
    
    // 辅助数组存储 x 中每个元素的邻居
    vector<vector<int>> neighbors(n + 1);
    for(int j=0; j

F. 子串反转的最大独立字符集

问题描述:

给定字符串 $S$,允许翻转任意一个子串一次。求翻转后,不包含重复字符的最长连续子串长度。字符集限制为 'a'-'t' (20 个字符)。

算法分析

反转操作等效于将原字符串切分为两段,交换后拼接(忽略顺序细节,本质是两个不重叠区间合并)。由于字符种类极少(20 种),可以用位掩码(Bitmask)表示一个集合中的字符组成。 首先预处理每个掩码在原串中能构成的最长无重复子串长度 $dp[mask]$。接着,利用 SOS DP(Sum Over Subsets)思想,计算 $g[mask] = \max(dp[submask])$,表示由掩码 $mask$ 代表的字符子集能构成的最大长度。最后答案即为 $\max(g[mask] + g[(1 \ll 20) - 1 - mask])$,即寻找两个互补的掩码组合能获得的最大总长。

代码实现

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

using namespace std;

int main() {
    ios::sync_with_stdio(0); cin.tie(0);
    string s;
    cin >> s;
    int len = s.length();
    
    // dp[state] 存储对应状态下的最大长度
    const int FULL_MASK = (1 << 20) - 1;
    vector<int> dp(FULL_MASK + 1, 0);
    
    // 第一阶段:枚举所有子串,填充 dp 数组
    for (int i = 0; i < len; ++i) {
        int mask = 0;
        for (int j = i; j < len; ++j) {
            int char_bit = s[j] - 'a';
            if (mask & (1 << char_bit)) break; // 有重复
            mask |= (1 << char_bit);
            dp[mask] = max(dp[mask], j - i + 1);
        }
    }
    
    // 第二阶段:SOS DP 松弛
    // 确保 dp[mask] 包含了其所有子集的可能最大值
    for (int i = 0; i < 20; ++i) {
        for (int mask = 0; mask <= FULL_MASK; ++mask) {
            if (mask & (1 << i)) {
                dp[mask] = max(dp[mask], dp[mask ^ (1 << i)]);
            }
        }
    }
    
    // 第三阶段:寻找互补掩码的最大值之和
    int max_len = 0;
    for (int mask = 0; mask <= FULL_MASK; ++mask) {
        max_len = max(max_len, dp[mask] + dp[FULL_MASK ^ mask]);
    }
    
    cout << max_len << "\n";
    return 0;
}

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

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