2025年第七届全国高校计算机能力挑战赛 决赛 C++组 编程题汇总

核心总结

编程题分为五类,A/B/C类各四道变种,D/E类各一道核心题。
能源传输线路才艺表演排列 两类题目为偏易,分别聚焦滑动窗口+双端队列、全排列枚举验证逻辑,侧重基础算法应用与边界处理;
工匠帝国 以动态规划为核心,需掌握01/完全背包组合应用,难度中等;
圆环馆连通性判断队列链判断 综合数学映射/并查集、LCA/线段树等,涉及多算法融合与大数据优化,难度中等偏上。

整体适配算法入门至进阶阶段学习者

文章目录

  • 核心总结
  • [A - 能源传输线路](#A - 能源传输线路)
    • [A1 - 能源传输线路(能量阈值600含,输出YES/NO)](#A1 - 能源传输线路(能量阈值600含,输出YES/NO))
    • [A2 - 能源传输线路(能量阈值500含,输出OK/-1)](#A2 - 能源传输线路(能量阈值500含,输出OK/-1))
    • [A3 - 能源传输线路(能量阈值500含,输出YES/NO)](#A3 - 能源传输线路(能量阈值500含,输出YES/NO))
    • [A4 - 能源传输线路(能量阈值600含,输出OK/-1)](#A4 - 能源传输线路(能量阈值600含,输出OK/-1))
  • [B - 工匠帝国](#B - 工匠帝国)
    • [B1 - 工匠帝国(价格阈值5000含,输出YES/NO)](#B1 - 工匠帝国(价格阈值5000含,输出YES/NO))
    • [B2 - 工匠帝国(价格阈值6000含,输出OK/-1)](#B2 - 工匠帝国(价格阈值6000含,输出OK/-1))
    • [B3 - 工匠帝国(价格阈值6000含,输出YES/NO)](#B3 - 工匠帝国(价格阈值6000含,输出YES/NO))
    • [B4 - 工匠帝国(价格阈值5000含,输出OK/-1)](#B4 - 工匠帝国(价格阈值5000含,输出OK/-1))
  • [C - 才艺表演排列](#C - 才艺表演排列)
    • [C1 - 才艺表演排列(剔除类型N,输出YES/NO)](#C1 - 才艺表演排列(剔除类型N,输出YES/NO))
    • [C2 - 才艺表演排列(剔除类型P,输出OK/-1)](#C2 - 才艺表演排列(剔除类型P,输出OK/-1))
    • [C3 - 才艺表演排列(剔除类型P,输出YES/NO)](#C3 - 才艺表演排列(剔除类型P,输出YES/NO))
    • [C4 - 才艺表演排列(剔除类型N,输出OK/-1)](#C4 - 才艺表演排列(剔除类型N,输出OK/-1))
  • [D - 圆环馆连通性判断](#D - 圆环馆连通性判断)
  • [E - 队列链判断(树结构队列操作)](#E - 队列链判断(树结构队列操作))

A - 能源传输线路

相似题目差异总结

  • 核心差异:能量筛选阈值不同(600/500),输出标识不同(YES/NO / OK/-1),其余场景逻辑一致。
  • 核心算法:一致采用「筛选有效节点→滑动窗口+双端队列维护区间极值→收缩窗口保证差值合规→统计最长有效区间」的线性时间策略。
  • 优化点:vector预分配空间、关闭IO同步、双端队列O(1)维护区间最大/最小值,规避暴力枚举区间的O(n²)复杂度。

A1 - 能源传输线路(能量阈值600含,输出YES/NO)

题目描述:在银河系边缘的殖民星球上,科学家们建立了一个复杂的能源传输线路。网络由n个能源节点组成,每个节点有一定的初始能量值。由于星际风暴的影响,网络中的能量分布不均匀。为了优化能源分配,工程师希望找到一段最长的连续区间(至少包含两个能源节点),使得区间内能量最大值与最小值的差不超过阈值K,若存在这个区间,则首先输出一行"YES",若不存在这段区间,输出"NO"。因当前的技术限制,科学家们无法对能量超过600(含)的节点进行操作,因此计算线路时需暂时忽略这些节点。

运行条件

  • 总时限:5000毫秒
  • 单组时限:1000毫秒
  • 总内存:320 MB
  • 单组内存:64 MB

输入格式

  • 第一行包含两个整数n和k,表示节点数量和能量差阈值
  • 第二行包含n个整数a1,a2,...,an,表示每个节点的初始能量值

输出格式:输出包含一或两行,若所求区间存在,则首先输出一行"YES",之后第二行输出一个整数,表示满足条件的最长连续区间长度。若不存在这段区间,输出"NO"。

输入输出样例

  • 样例1:
    输入

    plaintext 复制代码
    8 3
    1 601 5 602 6 655 7 8

    输出

    plaintext 复制代码
    YES
    4
  • 样例2:
    输入

    plaintext 复制代码
    7 1
    1 10 20 30 40 566 543

    输出

    plaintext 复制代码
    NO

代码

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

int main() {
    // 关闭同步加速输入输出
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);

    int node_count, diff_threshold;
    cin >> node_count >> diff_threshold;

    // 存储筛选后的有效节点(能量<600)
    vector<int> valid_nodes;
    valid_nodes.reserve(node_count);  // 预分配空间提升效率

    // 筛选符合条件的节点
    for (int i = 0; i < node_count; ++i) {
        int energy;
        cin >> energy;
        if (energy < 600) {  // 忽略能量≥600的节点
            valid_nodes.push_back(energy);
        }
    }

    int valid_count = valid_nodes.size();
    int left_ptr = 0;  // 滑动窗口左边界
    int max_length = 0; // 记录最长有效区间长度

    // 双端队列:分别维护区间内最大值和最小值的索引
    deque<int> max_idx_deque;
    deque<int> min_idx_deque;

    // 滑动窗口右边界遍历
    for (int right_ptr = 0; right_ptr < valid_count; ++right_ptr) {
        // 维护最大值队列:队尾元素≤当前元素则弹出,保证队首是最大值索引
        while (!max_idx_deque.empty() && valid_nodes[max_idx_deque.back()] <= valid_nodes[right_ptr]) {
            max_idx_deque.pop_back();
        }
        max_idx_deque.push_back(right_ptr);

        // 维护最小值队列:队尾元素≥当前元素则弹出,保证队首是最小值索引
        while (!min_idx_deque.empty() && valid_nodes[min_idx_deque.back()] >= valid_nodes[right_ptr]) {
            min_idx_deque.pop_back();
        }
        min_idx_deque.push_back(right_ptr);

        // 若区间最大最小差值超过阈值,移动左边界收缩窗口
        while (!max_idx_deque.empty() && !min_idx_deque.empty()) {
            int current_max = valid_nodes[max_idx_deque.front()];
            int current_min = valid_nodes[min_idx_deque.front()];
            if (current_max - current_min > diff_threshold) {
                // 左边界移动时,移除队列中超出窗口的索引
                if (max_idx_deque.front() == left_ptr) {
                    max_idx_deque.pop_front();
                }
                if (min_idx_deque.front() == left_ptr) {
                    min_idx_deque.pop_front();
                }
                left_ptr++;
            } else {
                break;  // 差值符合条件,无需收缩
            }
        }

        // 更新最长区间长度
        int current_window_len = right_ptr - left_ptr + 1;
        if (current_window_len > max_length) {
            max_length = current_window_len;
        }
    }

    // 输出结果:至少包含两个节点才有效
    if (max_length >= 2) {
        cout << "YES\n" << max_length;
    } else {
        cout << "NO";
    }

    return 0;
}

A2 - 能源传输线路(能量阈值500含,输出OK/-1)

题目描述:在银河系边缘的殖民星球上,科学家们建立了一个复杂的能源传输线路。网络由n个能源节点组成,每个节点有一定的初始能量值。由于星际风暴的影响,网络中的能量分布不均匀。为了优化能源分配,工程师希望找到一段最长的连续区间(至少包含两个能源节点),使得区间内能量最大值与最小值的差不超过给定的阈值k,若存在这个区间,则首先输出一行"OK",若不存在这段区间,输出"-1"。因当前的技术限制,科学家们无法对能量超过500(含)的节点进行操作,因此计算线路时需暂时忽略这些节点。

运行条件

  • 总时限:5000毫秒
  • 单组时限:1000毫秒
  • 总内存:320 MB
  • 单组内存:64 MB

输入格式

  • 第一行包含两个整数n和k,表示节点数量和能量差阈值
  • 第二行包含n个整数a1,a2,...,an,表示每个节点的初始能量值

输出格式:输出包含一或两行,若所求区间存在,则首先输出一行"OK",之后第二行输出一个整数,表示满足条件的最长连续区间长度。若不存在这段区间,输出"-1"。

输入输出样例

  • 样例1:
    输入

    plaintext 复制代码
    8 3
    1 501 5 502 6 555 7 8

    输出

    plaintext 复制代码
    OK
    4
  • 样例2:
    输入

    plaintext 复制代码
    7 1
    1 10 20 30 40 566 543

    输出

    plaintext 复制代码
    -1

代码

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

int main() {
    // 加速输入输出效率
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    int n_total, k_limit;
    cin >> n_total >> k_limit;

    // 存储能量<500的有效节点
    vector<int> qualified_nodes;
    qualified_nodes.reserve(n_total);

    // 筛选有效节点,排除能量≥500的节点
    for (int i = 0; i < n_total; ++i) {
        int val;
        cin >> val;
        if (val < 500) {
            qualified_nodes.emplace_back(val);
        }
    }

    int m = qualified_nodes.size();
    int left = 0;
    int longest_len = 0;

    // 双端队列维护区间最大、最小值索引
    deque<int> max_deque;
    deque<int> min_deque;

    // 扩展窗口右边界
    for (int right = 0; right < m; ++right) {
        // 维护最大值队列:保持队首为当前区间最大值
        while (!max_deque.empty() && qualified_nodes[max_deque.back()] <= qualified_nodes[right]) {
            max_deque.pop_back();
        }
        max_deque.push_back(right);

        // 维护最小值队列:保持队首为当前区间最小值
        while (!min_deque.empty() && qualified_nodes[min_deque.back()] >= qualified_nodes[right]) {
            min_deque.pop_back();
        }
        min_deque.push_back(right);

        // 收缩窗口:当最大最小差值超过阈值时,移动左边界
        while (true) {
            if (max_deque.empty() || min_deque.empty()) break;
            int range_max = qualified_nodes[max_deque.front()];
            int range_min = qualified_nodes[min_deque.front()];
            if (range_max - range_min > k_limit) {
                if (max_deque.front() == left) max_deque.pop_front();
                if (min_deque.front() == left) min_deque.pop_front();
                left++;
            } else {
                break;
            }
        }

        // 更新最长区间长度
        int curr_len = right - left + 1;
        if (curr_len > longest_len) {
            longest_len = curr_len;
        }
    }

    // 输出结果:区间长度至少为2才符合要求
    if (longest_len >= 2) {
        cout << "OK\n" << longest_len;
    } else {
        cout << "-1";
    }

    return 0;
}

A3 - 能源传输线路(能量阈值500含,输出YES/NO)

题目描述:在银河系边缘的殖民星球上,科学家们建立了一个复杂的能源传输线路。网络由n个能源节点组成,每个节点有一定的初始能量值。由于星际风暴的影响,网络中的能量分布不均匀。为了优化能源分配,工程师希望找到一段最长的连续区间(至少包含两个能源节点),使得区间内能量最大值与最小值的差不超过阈值K,若存在这个区间,则首先输出一行"YES",若不存在这段区间,输出"NO"。因当前的技术限制,科学家们无法对能量超过500(含)的节点进行操作,因此计算线路时需暂时忽略这些节点。

运行条件

  • 总时限:5000毫秒
  • 单组时限:1000毫秒
  • 总内存:320 MB
  • 单组内存:64 MB

输入格式

  • 第一行包含两个整数n和k,表示节点数量和能量差阈值
  • 第二行包含n个整数a1,a2,...,an,表示每个节点的初始能量值

输出格式:输出包含一或两行,若所求区间存在,则首先输出一行"YES",之后第二行输出一个整数,表示满足条件的最长连续区间长度。若不存在这段区间,输出"NO"。

输入输出样例

  • 样例1:
    输入

    plaintext 复制代码
    8 3
    1 501 5 502 6 555 7 8

    输出

    plaintext 复制代码
    YES
    4
  • 样例2:
    输入

    plaintext 复制代码
    7 1
    1 10 20 30 40 566 543

    输出

    plaintext 复制代码
    NO

代码

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);

    int node_num, threshold;
    cin >> node_num >> threshold;

    // 收集有效节点(能量小于500)
    vector<int> valid_energy;
    for (int i = 0; i < node_num; ++i) {
        int e;
        cin >> e;
        if (e < 500) {
            valid_energy.push_back(e);
        }
    }

    int m = valid_energy.size();
    int left = 0;
    int max_valid_len = 0;

    // 双端队列:存储索引,用于快速获取区间最大、最小值
    deque<int> max_queue;
    deque<int> min_queue;

    // 滑动窗口遍历所有可能的右边界
    for (int right = 0; right < m; ++right) {
        // 维护最大值队列:移除所有小于当前元素的队尾元素
        while (!max_queue.empty() && valid_energy[max_queue.back()] <= valid_energy[right]) {
            max_queue.pop_back();
        }
        max_queue.push_back(right);

        // 维护最小值队列:移除所有大于当前元素的队尾元素
        while (!min_queue.empty() && valid_energy[min_queue.back()] >= valid_energy[right]) {
            min_queue.pop_back();
        }
        min_queue.push_back(right);

        // 检查当前区间是否符合条件,不符合则移动左边界
        while (!max_queue.empty() && !min_queue.empty()) {
            int current_max = valid_energy[max_queue.front()];
            int current_min = valid_energy[min_queue.front()];
            if (current_max - current_min > threshold) {
                // 移除超出窗口的索引
                if (max_queue.front() == left) max_queue.pop_front();
                if (min_queue.front() == left) min_queue.pop_front();
                left++;
            } else {
                break;
            }
        }

        // 记录当前窗口的最大长度
        int current_len = right - left + 1;
        if (current_len > max_valid_len) {
            max_valid_len = current_len;
        }
    }

    // 判断是否存在至少两个节点的有效区间
    if (max_valid_len >= 2) {
        cout << "YES\n" << max_valid_len;
    } else {
        cout << "NO";
    }

    return 0;
}

A4 - 能源传输线路(能量阈值600含,输出OK/-1)

题目描述:在银河系边缘的殖民星球上,科学家们建立了一个复杂的能源传输线路。网络由n个能源节点组成,每个节点有一定的初始能量值。由于星际风暴的影响,网络中的能量分布不均匀。为了优化能源分配,工程师希望找到一段最长的连续区间(至少包含两个能源节点),使得区间内能量最大值与最小值的差不超过给定的阈值k,若存在这个区间,则首先输出一行"OK",若不存在这段区间,输出"-1"。因当前的技术限制,科学家们无法对能量超过600(含)的节点进行操作,因此计算线路时需暂时忽略这些节点。

运行条件

  • 总时限:5000毫秒
  • 单组时限:1000毫秒
  • 总内存:320 MB
  • 单组内存:64 MB

输入格式

  • 第一行包含两个整数n和k,表示节点数量和能量差阈值
  • 第二行包含n个整数a1,a2,...,an,表示每个节点的初始能量值

输出格式:输出包含一或两行,若所求区间存在,则首先输出一行"OK",之后第二行输出一个整数,表示满足条件的最长连续区间长度。若不存在这段区间,输出"-1"。

输入输出样例

  • 样例1:
    输入

    plaintext 复制代码
    8 3
    1 601 5 602 6 655 7 8

    输出

    plaintext 复制代码
    OK
    4
  • 样例2:
    输入

    plaintext 复制代码
    7 1
    1 10 20 30 40 566 543

    输出

    plaintext 复制代码
    -1

代码

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    int total_nodes, diff_limit;
    cin >> total_nodes >> diff_limit;

    // 筛选能量小于600的节点
    vector<int> filtered_nodes;
    filtered_nodes.reserve(total_nodes);
    for (int i = 0; i < total_nodes; ++i) {
        int energy_val;
        cin >> energy_val;
        if (energy_val < 600) {
            filtered_nodes.push_back(energy_val);
        }
    }

    int m = filtered_nodes.size();
    int left_bound = 0;
    int max_len = 0;

    // 双端队列维护区间最大、最小值索引
    deque<int> max_idx_q;
    deque<int> min_idx_q;

    // 遍历右边界,扩展窗口
    for (int right_bound = 0; right_bound < m; ++right_bound) {
        // 维护最大值队列:保证队首是当前区间最大值
        while (!max_idx_q.empty() && filtered_nodes[max_idx_q.back()] <= filtered_nodes[right_bound]) {
            max_idx_q.pop_back();
        }
        max_idx_q.push_back(right_bound);

        // 维护最小值队列:保证队首是当前区间最小值
        while (!min_idx_q.empty() && filtered_nodes[min_idx_q.back()] >= filtered_nodes[right_bound]) {
            min_idx_q.pop_back();
        }
        min_idx_q.push_back(right_bound);

        // 收缩窗口:当差值超过阈值时移动左边界
        while (!max_idx_q.empty() && !min_idx_q.empty()) {
            int range_max = filtered_nodes[max_idx_q.front()];
            int range_min = filtered_nodes[min_idx_q.front()];
            if (range_max - range_min > diff_limit) {
                if (max_idx_q.front() == left_bound) max_idx_q.pop_front();
                if (min_idx_q.front() == left_bound) min_idx_q.pop_front();
                left_bound++;
            } else {
                break;
            }
        }

        // 更新最长区间长度
        int window_len = right_bound - left_bound + 1;
        if (window_len > max_len) {
            max_len = window_len;
        }
    }

    // 输出结果
    if (max_len >= 2) {
        cout << "OK\n" << max_len;
    } else {
        cout << "-1";
    }

    return 0;
}

B - 工匠帝国

相似题目差异总结

  • 核心差异:价格筛选阈值不同(5000/6000),输出标识不同(YES/NO / OK/-1),其余问题模型一致。
  • 核心算法:一致采用「筛选有效增益包→01背包处理首次购买+完全背包处理重复购买→DP数组维护最小花费→查询超额声望的最小花费」的动态规划策略。
  • 优化点:DP数组预留超额声望空间、INF值标记无效状态、逆序/顺序遍历区分背包类型,保证大数据量下的时间效率。

B1 - 工匠帝国(价格阈值5000含,输出YES/NO)

题目描述:在一个工匠帝国,每年都会举行盛大的工艺盛典。来自各地的工匠们会在数日内展示他们的技艺,以获得评审团的声望值。经过多日竞争,各位工匠已经累积了一定的声望。盛典的最后一天,工匠们可以购买各种增益包来提升自己的声望值。每种增益包都可以无限次购买,且有两条特别规则:每种增益包第一次购买时,会额外获得一次性的首购奖励;之后再次购买同一种包,只能获得其基础声望。最终目标是:在最后一天获得至少S点额外声望,以争夺最终冠军。然而,增益包是有价格的,每位工匠都希望在达到至少S声望的前提下,花费最少的金钱,且若某增益包的价格超过5000(含),则认为该增益包为假货,不能够购买。现在请你帮某位工匠计算:若要达到至少S声望,最少需要花费多少钱?若能够通过购买增益包达到S点声望,则首先输出一行"YES",若无论如何都达不到,输出"NO"。

运行条件

  • 总时限:5000毫秒
  • 单组时限:1000毫秒
  • 总内存:320 MB
  • 单组内存:64 MB

输入格式

  • 第一行输入两个整数n和S,表示增益包数量和目标声望值。
  • 接下来n行,每行包含3个整数cost_i、base_value_i和bonus_i,分别代表第i种增益包的价格、该包每次购买基础获得的声望和该包首次购买额外获得的声望。

输出格式:输出包含一或两行,若能够通过购买增益包达到S点声望,则首先输出一行"YES",接下来第二行输出一个整数,表示达到至少S声望所需的最小花费。若无论如何都达不到,输出"NO"。

输入输出样例

  • 样例1:
    输入

    plaintext 复制代码
    1 100
    10 0 5

    输出

    plaintext 复制代码
    NO
  • 样例2:
    输入

    plaintext 复制代码
    3 9
    3 4 1
    4 6 0
    5100 10000 5000

    输出

    plaintext 复制代码
    YES
    6

代码

cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

const long long INF = 1e18;  // 无穷大表示不可达

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int pack_num, target_rep;
    if (!(cin >> pack_num >> target_rep)) {
        cout << "NO" << endl;
        return 0;
    }

    // dp[rep] 表示获得rep点声望的最小花费
    int dp_size = target_rep + 20005;  // 预留额外空间处理超额声望
    vector<long long> dp(dp_size, INF);
    dp[0] = 0;  // 初始状态:0声望花费0元

    // 遍历每个增益包
    for (int i = 0; i < pack_num; ++i) {
        int price, base_rep, extra_rep;
        cin >> price >> base_rep >> extra_rep;

        // 跳过价格≥5000的假货
        if (price >= 5000) {
            continue;
        }

        // 首次购买的总声望(基础+额外)
        int first_buy_rep = base_rep + extra_rep;
        // 后续购买的声望(仅基础)
        int later_buy_rep = base_rep;

        // 处理首次购买:01背包(每个包仅能首次购买一次)
        for (int j = dp_size - 1; j >= first_buy_rep; --j) {
            if (dp[j - first_buy_rep] != INF) {
                dp[j] = min(dp[j], dp[j - first_buy_rep] + price);
            }
        }

        // 处理后续购买:完全背包(可无限次购买)
        if (later_buy_rep > 0) {  // 基础声望为0时后续购买无意义
            for (int j = later_buy_rep; j < dp_size; ++j) {
                if (dp[j - later_buy_rep] != INF) {
                    dp[j] = min(dp[j], dp[j - later_buy_rep] + price);
                }
            }
        }
    }

    // 查找达到目标声望的最小花费(包括超额情况)
    long long min_cost = INF;
    for (int j = target_rep; j < dp_size; ++j) {
        if (dp[j] < min_cost) {
            min_cost = dp[j];
        }
    }

    // 输出结果
    if (min_cost == INF) {
        cout << "NO" << endl;
    } else {
        cout << "YES" << endl;
        cout << min_cost << endl;
    }

    return 0;
}

B2 - 工匠帝国(价格阈值6000含,输出OK/-1)

题目描述:在一个工匠帝国,每年都会举行盛大的工艺盛典。来自各地的工匠们会在数日内展示他们的技艺,以获得评审团的声望值。经过多日竞争,各位工匠已经累积了一定的声望。盛典的最后一天,工匠们可以购买各种增益包来提升自己的声望值。每种增益包都可以无限次购买,且有两条特别规则:每种增益包第一次购买时,会额外获得一次性的首购奖励;之后再次购买同一种包,只能获得其基础声望。最终目标是:在最后一天获得至少S点额外声望,以争夺最终冠军。然而,增益包是有价格的,每位工匠都希望在达到至少S声望的前提下,花费最少的金钱,且若某增益包的价格超过6000(含),则认为该增益包为假货,不能够购买。现在请你帮某位工匠计算:若要达到至少S声望,最少需要花费多少钱?若能够通过购买增益包达到S点声望,则首先输出一行"OK",若无论如何都达不到,输出"-1"。

运行条件

  • 总时限:5000毫秒
  • 单组时限:1000毫秒
  • 总内存:320 MB
  • 单组内存:64 MB

输入格式

  • 第一行输入两个整数n和S,表示增益包数量和目标声望值。
  • 接下来输入n行,每行包含3个整数cost_i、base_value_i和bonus_i,分别代表第i种增益包的价格、该包每次购买基础获得的声望和该包首次购买额外获得的声望。

输出格式:输出包含一或两行,若能够通过购买增益包达到S点声望,则首先输出一行"OK",接下来第二行输出一个整数,表示达到至少S声望所需的最小花费。若无论如何都达不到,输出"-1"。

输入输出样例

  • 样例1:
    输入

    plaintext 复制代码
    1 100
    10 0 5

    输出

    plaintext 复制代码
    -1
  • 样例2:
    输入

    plaintext 复制代码
    3 9
    3 4 1
    4 6 0
    6100 10000 5000

    输出

    plaintext 复制代码
    OK
    6

代码

cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

const long long INF = 1e18;  // 定义无穷大,代表不可达状态

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n_packs, target_s;
    if (!(cin >> n_packs >> target_s)) {
        cout << "-1" << endl;
        return 0;
    }

    // dp数组:dp[r]表示获得r点声望的最小花费
    int dp_max = target_s + 20005;  // 预留20005的超额空间
    vector<long long> dp(dp_max, INF);
    dp[0] = 0;  // 初始状态:0声望花费0元

    // 遍历每个增益包
    for (int i = 0; i < n_packs; ++i) {
        int cost, base, bonus;
        cin >> cost >> base >> bonus;

        // 跳过价格≥6000的假货
        if (cost >= 6000) {
            continue;
        }

        int first_rep = base + bonus;  // 首次购买获得的总声望
        int repeat_rep = base;         // 重复购买获得的声望

        // 处理首次购买(01背包问题,逆序遍历)
        for (int r = dp_max - 1; r >= first_rep; --r) {
            if (dp[r - first_rep] != INF) {
                dp[r] = min(dp[r], dp[r - first_rep] + cost);
            }
        }

        // 处理重复购买(完全背包问题,顺序遍历)
        if (repeat_rep > 0) {
            for (int r = repeat_rep; r < dp_max; ++r) {
                if (dp[r - repeat_rep] != INF) {
                    dp[r] = min(dp[r], dp[r - repeat_rep] + cost);
                }
            }
        }
    }

    // 查找达到目标声望的最小花费
    long long min_expense = INF;
    for (int r = target_s; r < dp_max; ++r) {
        if (dp[r] < min_expense) {
            min_expense = dp[r];
        }
    }

    // 输出结果
    if (min_expense == INF) {
        cout << "-1" << endl;
    } else {
        cout << "OK" << endl;
        cout << min_expense << endl;
    }

    return 0;
}

B3 - 工匠帝国(价格阈值6000含,输出YES/NO)

题目描述:在一个工匠帝国,每年都会举行盛大的工艺盛典。来自各地的工匠们会在数日内展示他们的技艺,以获得评审团的声望值。经过多日竞争,各位工匠已经累积了一定的声望。盛典的最后一天,工匠们可以购买各种增益包来提升自己的声望值。每种增益包都可以无限次购买,且有两条特别规则:每种增益包第一次购买时,会额外获得一次性的首购奖励;之后再次购买同一种包,只能获得其基础声望。最终目标是:在最后一天获得至少S点额外声望,以争夺最终冠军。然而,增益包是有价格的,每位工匠都希望在达到至少S声望的前提下,花费最少的金钱,且若某增益包的价格超过6000(含),则认为该增益包为假货,不能够购买。现在请你帮某位工匠计算:若要达到至少S声望,最少需要花费多少钱?若能够通过购买增益包达到S点声望,则首先输出一行"YES",若无论如何都达不到,输出"NO"。

运行条件

  • 总时限:5000毫秒
  • 单组时限:1000毫秒
  • 总内存:320 MB
  • 单组内存:64 MB

输入格式

  • 第一行输入两个整数n和S,表示增益包数量和目标声望值。
  • 接下来n行,每行包含3个整数cost_i、base_value_i和bonus_i,分别代表第i种增益包的价格、该包每次购买基础获得的声望和该包首次购买额外获得的声望。

输出格式:输出包含一或两行,若能够通过购买增益包达到S点声望,则首先输出一行"YES",接下来第二行输出一个整数,表示达到至少S声望所需的最小花费。若无论如何都达不到,输出"NO"。

输入输出样例

  • 样例1:
    输入

    plaintext 复制代码
    1 100
    10 0 5

    输出

    plaintext 复制代码
    NO
  • 样例2:
    输入

    plaintext 复制代码
    3 9
    3 4 1
    4 6 0
    6100 10000 5000

    输出

    plaintext 复制代码
    YES
    6

代码

cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

const long long INF = 1e18;  // 无穷大标记

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int pack_count, target_rep;
    cin >> pack_count >> target_rep;

    // dp数组:存储获得对应声望的最小花费
    int dp_len = target_rep + 20005;
    vector<long long> min_cost(dp_len, INF);
    min_cost[0] = 0;  // 初始状态

    // 处理每个增益包
    for (int i = 0; i < pack_count; ++i) {
        int c, b, bo;
        cin >> c >> b >> bo;

        // 排除价格≥6000的假货
        if (c >= 6000) {
            continue;
        }

        int first_get = b + bo;  // 首次购买总声望
        int more_get = b;        // 后续购买声望

        // 首次购买:01背包,逆序遍历避免重复
        for (int j = dp_len - 1; j >= first_get; --j) {
            if (min_cost[j - first_get] != INF) {
                min_cost[j] = min(min_cost[j], min_cost[j - first_get] + c);
            }
        }

        // 后续购买:完全背包,顺序遍历允许重复
        if (more_get > 0) {
            for (int j = more_get; j < dp_len; ++j) {
                if (min_cost[j - more_get] != INF) {
                    min_cost[j] = min(min_cost[j], min_cost[j - more_get] + c);
                }
            }
        }
    }

    // 查找最小花费(包括超额声望)
    long long result = INF;
    for (int j = target_rep; j < dp_len; ++j) {
        if (min_cost[j] < result) {
            result = min_cost[j];
        }
    }

    // 输出结果
    if (result == INF) {
        cout << "NO" << endl;
    } else {
        cout << "YES" << endl;
        cout << result << endl;
    }

    return 0;
}

B4 - 工匠帝国(价格阈值5000含,输出OK/-1)

题目描述:在一个工匠帝国,每年都会举行盛大的工艺盛典。来自各地的工匠们会在数日内展示他们的技艺,以获得评审团的声望值。经过多日竞争,各位工匠已经累积了一定的声望。盛典的最后一天,工匠们可以购买各种增益包来提升自己的声望值。每种增益包都可以无限次购买,且有两条特别规则:每种增益包第一次购买时,会额外获得一次性的首购奖励;之后再次购买同一种包,只能获得其基础声望。最终目标是:在最后一天获得至少S点额外声望,以争夺最终冠军。然而,增益包是有价格的,每位工匠都希望在达到至少S声望的前提下,花费最少的金钱,且若某增益包的价格超过5000(含),则认为该增益包为假货,不能够购买。现在请你帮某位工匠计算:若要达到至少S声望,最少需要花费多少钱?若能够通过购买增益包达到S点声望,则首先输出一行"OK",若无论如何都达不到,输出"-1"。

运行条件

  • 总时限:5000毫秒
  • 单组时限:1000毫秒
  • 总内存:320 MB
  • 单组内存:64 MB

输入格式

  • 第一行输入两个整数n和S,表示增益包数量和目标声望值。
  • 接下来输入n行,每行包含3个整数cost_i、base_value_i和bonus_i,分别代表第i种增益包的价格、该包每次购买基础获得的声望和该包首次购买额外获得的声望。

输出格式:输出包含一或两行,若能够通过购买增益包达到S点声望,则首先输出一行"OK",接下来第二行输出一个整数,表示达到至少S声望所需的最小花费。若无论如何都达不到,输出"-1"。

输入输出样例

  • 样例1:
    输入

    plaintext 复制代码
    1 100
    10 0 5

    输出

    plaintext 复制代码
    -1
  • 样例2:
    输入

    plaintext 复制代码
    3 9
    3 4 1
    4 6 0
    5100 10000 5000

    输出

    plaintext 复制代码
    OK
    6

代码

cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

const long long INF = 1e18;  // 定义无穷大

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n, s_target;
    if (!(cin >> n >> s_target)) {
        cout << "-1" << endl;
        return 0;
    }

    // dp[r] 表示获得r点声望的最小花费
    int dp_cap = s_target + 20005;
    vector<long long> dp(dp_cap, INF);
    dp[0] = 0;  // 初始状态

    // 遍历所有增益包
    for (int i = 0; i < n; ++i) {
        int cost, base_rep, bonus_rep;
        cin >> cost >> base_rep >> bonus_rep;

        // 跳过价格≥5000的假货
        if (cost >= 5000) {
            continue;
        }

        int first_total = base_rep + bonus_rep;  // 首次购买总声望
        int repeat_total = base_rep;             // 重复购买声望

        // 处理首次购买(01背包)
        for (int r = dp_cap - 1; r >= first_total; --r) {
            if (dp[r - first_total] != INF) {
                dp[r] = min(dp[r], dp[r - first_total] + cost);
            }
        }

        // 处理重复购买(完全背包)
        if (repeat_total > 0) {
            for (int r = repeat_total; r < dp_cap; ++r) {
                if (dp[r - repeat_total] != INF) {
                    dp[r] = min(dp[r], dp[r - repeat_total] + cost);
                }
            }
        }
    }

    // 查找最小花费
    long long min_money = INF;
    for (int r = s_target; r < dp_cap; ++r) {
        if (dp[r] < min_money) {
            min_money = dp[r];
        }
    }

    // 输出结果
    if (min_money == INF) {
        cout << "-1" << endl;
    } else {
        cout << "OK" << endl;
        cout << min_money << endl;
    }

    return 0;
}

C - 才艺表演排列

相似题目差异总结

  • 核心差异:剔除的才艺类型不同(N/P),输出标识不同(YES/NO / OK/-1),排列验证逻辑一致。
  • 核心算法:一致采用「筛选有效学生→next_permutation生成全排列→验证相邻才艺类型→收集并输出有效排列」的枚举验证策略。
  • 优化点:利用STL内置排列函数生成字典序序列、vector存储有效结果,避免手动排列生成的复杂逻辑,保证代码简洁性。

C1 - 才艺表演排列(剔除类型N,输出YES/NO)

题目描述:学校要举办一场才艺表演,有n个学生报名参加。每个学生有一个编号(从1到n)和一个才艺类型(用大写字母表示,例如,A代表唱歌,B代表跳舞,C代表乐器等)。老师发现有些才艺类型如果连续表演会影响效果,所以规定相同才艺类型的学生不能连续出场。另外,因表演主题限制,类型为"N"(小品)的才艺不能出现在本次表演中,排列顺序前需剔除才艺类型为"N"的同学,剔除后需对剩余的学生重新编号。请你帮助老师生成所有满足条件的出场顺序,给定一个整数n和一个长度为n的字符串,表示n个学生的才艺类型。要求生成所有可能的出场顺序,并且满足相邻位置的学生才艺类型不能相同的条件,按字典序输出所有满足条件的序列。如果能够输出若干的出场顺序,则先输出一行"YES",如果没有满足条件的序列,则输出"NO"。

运行条件

  • 总时限:5000毫秒
  • 单组时限:1000毫秒
  • 总内存:320 MB
  • 单组内存:64 MB

输入格式

  • 第一行是一个整数n。
  • 第二行是一个长度为n的字符串,由大写字母组成,表示第1到第n个学生的才艺类型。

输出格式:输出包含若干行,若能够输出若干的出场顺序,则先输出一行"YES",接下来若干行输出所有满足条件的出场顺序,每行一个序列。否则只输出一个"NO"。

输入输出样例

  • 样例1:
    输入

    plaintext 复制代码
    5
    ANBNC

    输出

    plaintext 复制代码
    YES
    123
    132
    213
    231
    312
    321
  • 样例2:
    输入

    plaintext 复制代码
    5
    ANBNA

    输出

    plaintext 复制代码
    YES
    123
    321

代码

cpp 复制代码
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <numeric>
using namespace std;

int main() {
    int student_count;
    if (!(cin >> student_count)) {
        return 0;
    }

    string talent_str;
    cin >> talent_str;

    // 筛选出才艺类型不是'N'的学生
    vector<char> valid_talents;
    for (char t : talent_str) {
        if (t != 'N') {
            valid_talents.push_back(t);
        }
    }

    int m = valid_talents.size();
    // 无有效学生时直接输出YES
    if (m == 0) {
        cout << "YES" << endl;
        return 0;
    }

    // 生成初始编号序列(1-based)
    vector<int> order(m);
    iota(order.begin(), order.end(), 1);

    vector<vector<int>> valid_orders;  // 存储所有有效排列

    // 遍历所有全排列
    do {
        bool is_valid = true;
        // 检查相邻学生才艺类型是否不同
        for (int i = 0; i < m - 1; ++i) {
            // 当前位置和下一个位置的才艺类型
            char curr_t = valid_talents[order[i] - 1];
            char next_t = valid_talents[order[i + 1] - 1];
            if (curr_t == next_t) {
                is_valid = false;
                break;
            }
        }
        // 有效排列加入结果集
        if (is_valid) {
            valid_orders.push_back(order);
        }
    } while (next_permutation(order.begin(), order.end()));

    // 输出结果
    if (valid_orders.empty()) {
        cout << "NO" << endl;
    } else {
        cout << "YES" << endl;
        for (auto &seq : valid_orders) {
            for (int num : seq) {
                cout << num;
            }
            cout << endl;
        }
    }

    return 0;
}

C2 - 才艺表演排列(剔除类型P,输出OK/-1)

题目描述:学校要举办一场才艺表演,有n个学生报名参加。每个学生有一个编号(从1到n)和一个才艺类型(用大写字母表示,例如,A代表唱歌,B代表跳舞,C代表乐器等)。老师发现有些才艺类型如果连续表演会影响效果,所以规定相同才艺类型的学生不能连续出场。另外,因表演主题限制,类型为"P"(小品)的才艺不能出现在本次表演中,排列顺序前需剔除才艺类型为"P"的同学,剔除后需对剩余的学生重新编号。请你帮助老师生成所有满足条件的出场顺序,给定一个整数n和一个长度为n的字符串,表示n个学生的才艺类型。要求生成所有可能的出场顺序,并且满足相邻位置的学生才艺类型不能相同的条件,按字典序输出所有满足条件的序列。如果能够输出若干的出场顺序,则先输出一行"OK",如果没有满足条件的序列,则输出"-1"。

运行条件

  • 总时限:5000毫秒
  • 单组时限:1000毫秒
  • 总内存:320 MB
  • 单组内存:64 MB

输入格式

  • 第一行是一个整数n。
  • 第二行是一个长度为n的字符串,由大写字母组成,表示第1到第n个学生的才艺类型。

输出格式:输出包含若干行,若能够输出若干的出场顺序,则先输出一行"OK",接下来若干行输出所有满足条件的出场顺序,每行一个序列。否则只输出一个"-1"。

输入输出样例

  • 样例1:
    输入

    plaintext 复制代码
    5
    APBPC

    输出

    plaintext 复制代码
    OK
    123
    132
    213
    231
    312
    321
  • 样例2:
    输入

    plaintext 复制代码
    5
    APBAP

    输出

    plaintext 复制代码
    OK
    123
    321

代码

cpp 复制代码
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <numeric>
using namespace std;

int main() {
    int n;
    if (!(cin >> n)) {
        return 0;
    }

    string talents;
    cin >> talents;

    // 筛选剔除类型为'P'的学生
    vector<char> filtered_talents;
    for (char c : talents) {
        if (c != 'P') {
            filtered_talents.push_back(c);
        }
    }

    int m = filtered_talents.size();
    // 无有效学生时输出OK
    if (m == 0) {
        cout << "OK" << endl;
        return 0;
    }

    // 生成1~m的编号序列
    vector<int> perm(m);
    iota(perm.begin(), perm.end(), 1);

    vector<vector<int>> valid_perms;  // 存储有效排列

    // 遍历所有全排列
    do {
        bool valid = true;
        // 检查相邻才艺是否相同
        for (int i = 0; i < m - 1; ++i) {
            int idx1 = perm[i] - 1;
            int idx2 = perm[i + 1] - 1;
            if (filtered_talents[idx1] == filtered_talents[idx2]) {
                valid = false;
                break;
            }
        }
        if (valid) {
            valid_perms.push_back(perm);
        }
    } while (next_permutation(perm.begin(), perm.end()));

    // 输出结果
    if (valid_perms.empty()) {
        cout << "-1" << endl;
    } else {
        cout << "OK" << endl;
        for (auto &p : valid_perms) {
            for (int num : p) {
                cout << num;
            }
            cout << endl;
        }
    }

    return 0;
}

C3 - 才艺表演排列(剔除类型P,输出YES/NO)

题目描述:学校要举办一场才艺表演,有n个学生报名参加。每个学生有一个编号(从1到n)和一个才艺类型(用大写字母表示,例如,A代表唱歌,B代表跳舞,C代表乐器等)。老师发现有些才艺类型如果连续表演会影响效果,所以规定相同才艺类型的学生不能连续出场。另外,因表演主题限制,类型为"P"(小品)的才艺不能出现在本次表演中,排列顺序前需剔除才艺类型为"P"的同学,剔除后需对剩余的学生重新编号。请你帮助老师生成所有满足条件的出场顺序,给定一个整数n和一个长度为n的字符串,表示n个学生的才艺类型。要求生成所有可能的出场顺序,并且满足相邻位置的学生才艺类型不能相同的条件,按字典序输出所有满足条件的序列。如果能够输出若干的出场顺序,则先输出一行"YES",如果没有满足条件的序列,则输出"NO"。

运行条件

  • 总时限:5000毫秒
  • 单组时限:1000毫秒
  • 总内存:320 MB
  • 单组内存:64 MB

输入格式

  • 第一行是一个整数n。
  • 第二行是一个长度为n的字符串,由大写字母组成,表示第1到第n个学生的才艺类型。

输出格式:输出包含若干行,若能够输出若干的出场顺序,则先输出一行"YES",接下来若干行输出所有满足条件的出场顺序,每行一个序列。否则只输出一个"NO"。

输入输出样例

  • 样例1:
    输入

    plaintext 复制代码
    5
    APBPC

    输出

    plaintext 复制代码
    YES
    123
    132
    213
    231
    312
    321
  • 样例2:
    输入

    plaintext 复制代码
    5
    APBAP

    输出

    plaintext 复制代码
    YES
    123
    321

代码

cpp 复制代码
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <numeric>
using namespace std;

int main() {
    int total_students;
    if (!(cin >> total_students)) {
        return 0;
    }

    string talent_types;
    cin >> talent_types;

    // 筛选有效学生(剔除类型'P')
    vector<char> valid_types;
    for (char t : talent_types) {
        if (t != 'P') {
            valid_types.push_back(t);
        }
    }

    int m = valid_types.size();
    if (m == 0) {
        cout << "YES" << endl;
        return 0;
    }

    // 生成编号序列
    vector<int> num_seq(m);
    iota(num_seq.begin(), num_seq.end(), 1);

    vector<vector<int>> valid_sequences;  // 存储有效排列

    // 遍历所有全排列
    do {
        bool is_ok = true;
        // 检查相邻才艺是否不同
        for (int i = 0; i < m - 1; ++i) {
            char t1 = valid_types[num_seq[i] - 1];
            char t2 = valid_types[num_seq[i + 1] - 1];
            if (t1 == t2) {
                is_ok = false;
                break;
            }
        }
        if (is_ok) {
            valid_sequences.push_back(num_seq);
        }
    } while (next_permutation(num_seq.begin(), num_seq.end()));

    // 输出结果
    if (valid_sequences.empty()) {
        cout << "NO" << endl;
    } else {
        cout << "YES" << endl;
        for (auto &seq : valid_sequences) {
            for (int num : seq) {
                cout << num;
            }
            cout << endl;
        }
    }

    return 0;
}

C4 - 才艺表演排列(剔除类型N,输出OK/-1)

题目描述:学校要举办一场才艺表演,有n个学生报名参加。每个学生有一个编号(从1到n)和一个才艺类型(用大写字母表示,例如,A代表唱歌,B代表跳舞,C代表乐器等)。老师发现有些才艺类型如果连续表演会影响效果,所以规定相同才艺类型的学生不能连续出场。另外,因表演主题限制,类型为"N"(小品)的才艺不能出现在本次表演中,排列顺序前需剔除才艺类型为"N"的同学,剔除后需对剩余的学生重新编号。请你帮助老师生成所有满足条件的出场顺序,给定一个整数n和一个长度为n的字符串,表示n个学生的才艺类型。要求生成所有可能的出场顺序,并且满足相邻位置的学生才艺类型不能相同的条件,按字典序输出所有满足条件的序列。如果能够输出若干的出场顺序,则先输出一行"OK",如果没有满足条件的序列,则输出"-1"。

运行条件

  • 总时限:5000毫秒
  • 单组时限:1000毫秒
  • 总内存:320 MB
  • 单组内存:64 MB

输入格式

  • 第一行是一个整数n。
  • 第二行是一个长度为n的字符串,由大写字母组成,表示第1到第n个学生的才艺类型。

输出格式:输出包含若干行,若能够输出若干的出场顺序,则先输出一行"OK",接下来若干行输出所有满足条件的出场顺序,每行一个序列。否则只输出一个"-1"。

输入输出样例

  • 样例1:
    输入

    plaintext 复制代码
    5
    ANBNC

    输出

    plaintext 复制代码
    OK
    123
    132
    213
    231
    312
    321
  • 样例2:
    输入

    plaintext 复制代码
    5
    ANBNA

    输出

    plaintext 复制代码
    OK
    123
    321

代码

cpp 复制代码
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <numeric>
using namespace std;

int main() {
    int n;
    if (!(cin >> n)) {
        return 0;
    }

    string s;
    cin >> s;

    // 剔除类型为'N'的学生
    vector<char> talents;
    for (char c : s) {
        if (c != 'N') {
            talents.push_back(c);
        }
    }

    int m = talents.size();
    if (m == 0) {
        cout << "OK" << endl;
        return 0;
    }

    // 生成1~m的编号
    vector<int> perm(m);
    iota(perm.begin(), perm.end(), 1);

    vector<vector<int>> valid_perms;  // 存储有效排列

    // 遍历所有全排列
    do {
        bool valid = true;
        // 检查相邻才艺是否相同
        for (int i = 0; i < m - 1; ++i) {
            int pos1 = perm[i] - 1;
            int pos2 = perm[i + 1] - 1;
            if (talents[pos1] == talents[pos2]) {
                valid = false;
                break;
            }
        }
        if (valid) {
            valid_perms.push_back(perm);
        }
    } while (next_permutation(perm.begin(), perm.end()));

    // 输出结果
    if (valid_perms.empty()) {
        cout << "-1" << endl;
    } else {
        cout << "OK" << endl;
        for (auto &p : valid_perms) {
            for (int num : p) {
                cout << num;
            }
            cout << endl;
        }
    }

    return 0;
}

D - 圆环馆连通性判断

  • 核心算法:采用「前缀和映射空间编号→并查集初始化→层间空间连通性数学计算→合并连通空间→查询根节点一致性」的并查集策略。
  • 优化点:并查集路径压缩、前缀和快速计算空间编号,保证1e6级查询的O(α(n))时间复杂度,适配大规模查询场景。

题目描述:Nike的家是圆环馆,其地图可以用一个圆环从外向内分为k层圆环表示(最外圈是第1层圆环,依次向内增加圆环)。第k层圆环上建p_i面墙把这层圆环等分。方便起见,保证各层圆环均在12点钟方向开始建墙。第i层圆环从12点钟方向顺时针开始数的第j个空间编号为 (i,j)(i,j从1开始)。内外层圆环段之间若有公共边,则互相连通。同一层的圆环段若被墙隔离,则互相无法连通。现在给出q个询问,每个询问给出4个整数i,j,k,l,你需要判断(i,j)和(k,l)是否存在一条连通的路径。

运行条件

  • 总时限:5000毫秒
  • 单组时限:1000毫秒
  • 总内存:320 MB
  • 单组内存:64 MB

输入格式

  • 第一行有两个整数 k 和 q。
  • 接下来一行,有k个整数用空格隔开,表示从外到内每个圆环等分成多少个圆环段。
  • 接下来q行,每行有4个整数i,j,k,l,如题目所述。

输出格式:输出一个长度为q的01串,其中若第i个询问答案为连通,则该01串第i个字符为1,否则为0。

输入输出样例

  • 样例1:
    输入

    plaintext 复制代码
    1 3
    3
    1 1 1 2
    1 2 1 3
    1 1 1 3

    输出

    plaintext 复制代码
    000
  • 样例2:
    输入

    plaintext 复制代码
    5 7
    2 4 6 8 10
    1 1 1 2
    1 1 2 1
    1 1 2 2
    1 1 2 3
    1 1 2 4
    1 1 5 5
    1 1 5 6

    输出

    plaintext 复制代码
    0110010

代码

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

const int MAX_LAYERS = 1005;    // 最大层数
const int MAX_SPACES = 2000005; // 最大空间总数

int parent[MAX_SPACES];  // 并查集父节点数组
int layer_segments[MAX_LAYERS]; // 每层的分段数
int prefix_sum[MAX_LAYERS];     // 前缀和,用于计算空间编号

// 并查集查找函数(路径压缩)
int find_root(int x) {
    if (parent[x] == x) {
        return x;
    }
    return parent[x] = find_root(parent[x]);
}

// 并查集合并函数
void union_sets(int x, int y) {
    int root_x = find_root(x);
    int root_y = find_root(y);
    if (root_x != root_y) {
        parent[root_x] = root_y;
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    int layer_count, query_count;
    cin >> layer_count >> query_count;

    // 读取每层的分段数
    for (int i = 1; i <= layer_count; ++i) {
        cin >> layer_segments[i];
    }

    // 计算前缀和,确定每层空间的起始编号
    prefix_sum[0] = 0;
    for (int i = 1; i <= layer_count; ++i) {
        prefix_sum[i] = prefix_sum[i - 1] + layer_segments[i];
    }

    // 初始化并查集:每个空间的父节点是自身
    for (int i = 1; i <= prefix_sum[layer_count]; ++i) {
        parent[i] = i;
    }

    // 合并相邻层的连通空间
    for (int i = 1; i < layer_count; ++i) {
        long long curr_layer_segs = layer_segments[i];    // 当前层分段数
        long long next_layer_segs = layer_segments[i + 1];// 下一层分段数

        // 遍历当前层的每个空间
        for (int j = 1; j <= curr_layer_segs; ++j) {
            // 计算当前空间在下层对应的连通区间
            long long left = (1LL * (j - 1) * next_layer_segs) / curr_layer_segs + 1;
            long long right = (1LL * j * next_layer_segs - 1) / curr_layer_segs + 1;

            // 当前空间的编号
            int curr_space = prefix_sum[i - 1] + j;

            // 合并当前空间与下层对应的所有空间
            for (long long x = left; x <= right; ++x) {
                int next_space = prefix_sum[i] + x;
                union_sets(curr_space, next_space);
            }
        }
    }

    // 处理每个查询
    string result;
    for (int q = 0; q < query_count; ++q) {
        int layer1, seg1, layer2, seg2;
        cin >> layer1 >> seg1 >> layer2 >> seg2;

        // 计算两个空间的编号
        int space1 = prefix_sum[layer1 - 1] + seg1;
        int space2 = prefix_sum[layer2 - 1] + seg2;

        // 判断是否连通
        if (find_root(space1) == find_root(space2)) {
            result += '1';
        } else {
            result += '0';
        }
    }

    // 输出结果
    cout << result << endl;

    return 0;
}

E - 队列链判断(树结构队列操作)

  • 核心算法:采用「DFS生成欧拉序→ST表预处理LCA→线段树维护队列元素的最小覆盖链→合并链信息验证是否共链」的树链查询策略。
  • 优化点:关闭IO同步、线段树4倍空间预分配、deque维护队列避免数组移动,适配1e6级操作量的线性时间复杂度。

题目描述:对于n个顶点和n-1条边组成的无向连通图,给q个操作。每个操作格式如下:假设有1个队列,初始为空。1 x 在队列的队尾加入一个点x(队中可以有重复元素);2 删除队首点。如果当前队为空,不执行操作;3 询问图中是否存在一条链,使得队列中所有点均在这一条链上。注:此处的1、2、3为每种操作的类型标识。

运行条件

  • 总时限:5000毫秒
  • 单组时限:1000毫秒
  • 总内存:320 MB
  • 单组内存:64 MB

输入格式

  • 第一行包含2个正整数 n,q (1<=n,q<=1e6)。
  • 接下来的 n-1 行中,每行给出两个用空格分隔的正整数,表示一条连接2个点的编号(从 1 到 n 编号)。
  • 接下来的 q 行中,每行给出1个操作,格式如前所述。

输出格式:输出一行01序列,长度为3类型操作的个数(只有3类型操作才进行询问)。若第i个3类型询问答案为是在一条链上,则第i位的序列为1,否则为0。

输入输出样例

  • 样例1:
    输入

    plaintext 复制代码
    7 7
    1 2
    1 3
    2 4
    2 5
    3 6
    3 7
    1 1
    1 2
    1 3
    1 4
    3
    1 5
    3

    输出

    plaintext 复制代码
    10
  • 样例2:
    输入

    plaintext 复制代码
    4 11
    1 2
    1 3
    1 4
    1 1
    1 2
    1 3
    1 4
    3
    2
    3
    2
    3
    2
    3

    输出

    plaintext 复制代码
    0011

代码

cpp 复制代码
#include <iostream>
#include <vector>
#include <deque>
#include <cmath>
#include <cstring>
#include <algorithm>
using namespace std;

// ===================== 常量定义 =====================
const int MAX_NODE = 1e6 + 10;    // 最大节点数
const int MAX_OP = 1e6 + 10;      // 最大操作数
const int LOG = 22;               // 稀疏表对数上限(2^22 > 2e6)
const int EULER_SIZE = 2 * MAX_NODE; // 欧拉序最大长度

// ===================== 树结构 & LCA 相关 =====================
// 邻接表存储树的边
struct Edge {
    int to;    // 边的终点
    int next;  // 下一条边的索引
} edges[2 * MAX_NODE]; // 无向图,边数翻倍

int head[MAX_NODE];    // 每个节点的邻接表头
int edge_cnt = 0;      // 边的计数器

// 欧拉序相关变量
int euler[EULER_SIZE]; // 存储欧拉序列
int first_pos[MAX_NODE];// 每个节点在欧拉序中第一次出现的位置
int depth[MAX_NODE];   // 每个节点的深度
int euler_cnt = 0;     // 欧拉序计数器

// ST表(稀疏表):用于快速查询LCA
int st_table[LOG][EULER_SIZE];
int log_table[EULER_SIZE]; // 预处理log2值,加速查询

// 添加无向边
inline void add_edge(int u, int v) {
    edges[++edge_cnt].to = v;
    edges[edge_cnt].next = head[u];
    head[u] = edge_cnt;

    edges[++edge_cnt].to = u;
    edges[edge_cnt].next = head[v];
    head[v] = edge_cnt;
}

// DFS生成欧拉序,同时记录节点深度和首次出现位置
void dfs(int u, int fa, int d) {
    depth[u] = d;                  // 记录当前节点深度
    euler[++euler_cnt] = u;        // 欧拉序入队
    first_pos[u] = euler_cnt;      // 记录首次出现位置
    // 遍历所有邻边
    for (int i = head[u]; i; i = edges[i].next) {
        int v = edges[i].to;
        if (v != fa) {             // 避免回边到父节点
            dfs(v, u, d + 1);
            euler[++euler_cnt] = u;// 回溯时再次入队
        }
    }
}

// 比较两个节点在欧拉序中的深度,返回深度更小的(用于ST表)
inline int min_depth(int a, int b) {
    return depth[a] < depth[b] ? a : b;
}

// 预处理ST表(用于LCA查询)
void build_st_table() {
    // 初始化ST表第一层(k=0)
    for (int i = 1; i <= euler_cnt; ++i) {
        st_table[0][i] = euler[i];
    }

    // 递推构建ST表
    for (int k = 1; k < LOG; ++k) {
        for (int i = 1; i + (1 << k) - 1 <= euler_cnt; ++i) {
            st_table[k][i] = min_depth(
                st_table[k-1][i],
                st_table[k-1][i + (1 << (k-1))]
            );
        }
    }

    // 预处理log_table:log_table[i] = floor(log2(i))
    log_table[1] = 0;
    for (int i = 2; i <= euler_cnt; ++i) {
        log_table[i] = log_table[i >> 1] + 1;
    }
}

// 查询u和v的最近公共祖先(LCA)
inline int query_lca(int u, int v) {
    int l = first_pos[u], r = first_pos[v];
    if (l > r) swap(l, r); // 保证l <= r
    int k = log_table[r - l + 1]; // 计算区间长度的log2值
    return min_depth(
        st_table[k][l],
        st_table[k][r - (1 << k) + 1]
    );
}

// 计算两个节点之间的距离(基于LCA)
inline int get_dist(int u, int v) {
    int lca = query_lca(u, v);
    return depth[u] + depth[v] - 2 * depth[lca];
}

// 判断点x是否在u-v的链上
inline bool is_on_chain(int x, int u, int v) {
    int d1 = get_dist(x, u);
    int d2 = get_dist(x, v);
    int d3 = get_dist(u, v);
    return (d1 + d2) == d3; // 距离和等于u-v总距离则在链上
}

// ===================== 线段树(维护队列的链信息) =====================
// 线段树节点:维护区间内所有点的最小覆盖链
struct SegNode {
    int u;          // 链的一个端点
    int v;          // 链的另一个端点
    bool is_valid;  // 区间内所有点是否在同一条链上
} seg_tree[4 * MAX_OP]; // 线段树数组(4倍空间)

deque<int> que; // 维护操作的队列
int op_idx = 0; // 队列元素的操作索引(用于线段树的位置映射)

// 合并两个子节点的链信息,得到父节点的信息
SegNode merge(const SegNode& left, const SegNode& right) {
    SegNode res;
    // 情况1:左区间无效,直接返回右区间
    if (!left.is_valid) {
        res = right;
        return res;
    }
    // 情况2:右区间无效,直接返回左区间
    if (!right.is_valid) {
        res = left;
        return res;
    }
    // 情况3:左右区间都有效,尝试合并链
    int u1 = left.u, v1 = left.v;
    int u2 = right.u, v2 = right.v;

    // 候选链端点:u1-v1, u1-v2, u2-v1, u2-v2 中找覆盖所有点的链
    // 先验证原左区间的链是否能覆盖右区间的所有点
    if (is_on_chain(u2, u1, v1) && is_on_chain(v2, u1, v1)) {
        res.u = u1;
        res.v = v1;
        res.is_valid = true;
        return res;
    }
    // 验证u1-v2是否能覆盖所有点
    if (is_on_chain(v1, u1, v2) && is_on_chain(u2, u1, v2)) {
        res.u = u1;
        res.v = v2;
        res.is_valid = true;
        return res;
    }
    // 验证u2-v1是否能覆盖所有点
    if (is_on_chain(u1, u2, v1) && is_on_chain(v2, u2, v1)) {
        res.u = u2;
        res.v = v1;
        res.is_valid = true;
        return res;
    }
    // 验证u2-v2是否能覆盖所有点
    if (is_on_chain(u1, u2, v2) && is_on_chain(v1, u2, v2)) {
        res.u = u2;
        res.v = v2;
        res.is_valid = true;
        return res;
    }
    // 所有候选链都无法覆盖,标记为无效
    res.is_valid = false;
    return res;
}

// 初始化线段树
void init_seg_tree(int node, int l, int r) {
    seg_tree[node].is_valid = false;
    if (l == r) return;
    int mid = (l + r) / 2;
    init_seg_tree(node << 1, l, mid);
    init_seg_tree(node << 1 | 1, mid + 1, r);
}

// 更新线段树的某个位置(对应队列中的一个元素)
void update_seg_tree(int node, int l, int r, int pos, int val, bool is_add) {
    if (l == r) {
        if (is_add) { // 添加元素:链端点就是自己,有效
            seg_tree[node].u = val;
            seg_tree[node].v = val;
            seg_tree[node].is_valid = true;
        } else { // 删除元素:标记为无效
            seg_tree[node].is_valid = false;
        }
        return;
    }
    int mid = (l + r) / 2;
    if (pos <= mid) {
        update_seg_tree(node << 1, l, mid, pos, val, is_add);
    } else {
        update_seg_tree(node << 1 | 1, mid + 1, r, pos, val, is_add);
    }
    // 合并左右子节点的信息
    seg_tree[node] = merge(seg_tree[node << 1], seg_tree[node << 1 | 1]);
}

// ===================== 主函数 =====================
int main() {
    // 优化cin/cout速度,适配1e6级数据量
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);

    // 初始化邻接表
    memset(head, 0, sizeof(head));
    edge_cnt = 0;

    // 输入n和q
    int n, q;
    cin >> n >> q;

    // 输入n-1条边,构建树
    for (int i = 1; i <= n - 1; ++i) {
        int u, v;
        cin >> u >> v;
        add_edge(u, v);
    }

    // DFS生成欧拉序(根节点选1,深度从1开始)
    dfs(1, 0, 1);
    // 构建ST表,预处理LCA查询
    build_st_table();

    // 初始化线段树(操作数最大为1e6)
    init_seg_tree(1, 1, MAX_OP);

    // 维护队列的操作索引(队首、队尾)
    int que_front = 1;
    int que_back = 0;
    // 存储查询结果的字符串
    string ans = "";

    // 处理q个操作
    while (q--) {
        int op_type;
        cin >> op_type;
        if (op_type == 1) { // 操作1:入队
            int x;
            cin >> x;
            que.push_back(x);
            que_back++;
            // 更新线段树:添加位置que_back的元素x
            update_seg_tree(1, 1, MAX_OP, que_back, x, true);
        } else if (op_type == 2) { // 操作2:出队
            if (!que.empty()) {
                // 更新线段树:删除位置que_front的元素
                update_seg_tree(1, 1, MAX_OP, que_front, 0, false);
                que.pop_front();
                que_front++;
            }
        } else if (op_type == 3) { // 操作3:查询
            if (que.empty()) { // 队列为空,默认在一条链上
                ans += '1';
            } else {
                // 查询线段树根节点的有效性
                ans += seg_tree[1].is_valid ? '1' : '0';
            }
        }
    }

    // 输出查询结果
    cout << ans << endl;

    return 0;
}
相关推荐
曼巴UE52 小时前
UE5 C++ 动态多播
java·开发语言
小小晓.2 小时前
Pinely Round 4 (Div. 1 + Div. 2)
c++·算法
SHOJYS2 小时前
学习离线处理 [CSP-J 2022 山东] 部署
数据结构·c++·学习·算法
steins_甲乙3 小时前
C++并发编程(3)——资源竞争下的安全栈
开发语言·c++·安全
煤球王子3 小时前
学而时习之:C++中的异常处理2
c++
请一直在路上3 小时前
python文件打包成exe(虚拟环境打包,减少体积)
开发语言·python
luguocaoyuan3 小时前
JavaScript性能优化实战技术学习大纲
开发语言·javascript·性能优化
仰泳的熊猫4 小时前
1084 Broken Keyboard
数据结构·c++·算法·pat考试
禁默4 小时前
“零消耗”调用优质模型:AI Ping结合Cline助我快速开发SVG工具,性能与官网无异
开发语言·php
我不会插花弄玉4 小时前
C++的内存管理【由浅入深-C++】
c++