CST编程题题解- from 黄老师

四道编程题C++完整题解

本文档包含CST 1-1-2 Interview、CST 1-2-1 Gift、CST 1-2-2 Graphics、CST 1-2-3 二维偏序四道题的完整题目描述解题思路C++可运行代码输入输出示例,代码均做输入输出加速+溢出处理,适配题目数据范围与时间/空间限制。

一、CST 1-1-2 Interview(圆桌面试顺序)

题目描述

某公司在对应聘者做过一轮笔试之后,从中选出n人继续进行面试,每位应聘者被分配了一个整数ID。
为公平起见,组织者决定利用会议室外的圆桌,按以下方法"随机"确定面试顺序:第一个到达的应聘者在圆桌周围任意选择一个位置坐下;此后到达的每位应聘者都从前一应聘者出发,沿逆时针方向围圆桌走过m人(前一应聘者算作走过的第1人,同一人可能经过多次),并紧邻第m人右侧就座;所有应聘者到齐后,从最后到达者出发,绕圆桌以顺时针方向为序进行面试。
这里假定应聘者到达的时刻互异,且相对的就坐位置确定后,左、右两人之间总能插入一把椅子。
试编写一个程序,确定面试顺序。

11-

输入
共2行。
第1行包含两个整数,n和m。
第2行包含n个整数,表示先后到达的n个应聘者的ID。

输出
共1行。以空格分隔的n个整数,分别表示顺次进行面试的应聘者的ID。

解题思路

  1. 第一个应聘者直接入列,后续应聘者从前一人位置逆时针数m人,在第m右侧插入新位置;
  2. 利用动态数组模拟环形圆桌结构,通过取模运算计算环形位置,避免数组越界;
  3. 所有应聘者入座后,从最后入座者的位置开始,顺时针遍历(位置逐次减1并取模,加n避免负数),得到最终面试顺序。

C++ 完整代码

#include <iostream>
#include <vector>
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    int n, m;
    cin >> n >> m;
    vector<char> ids(n);
    for (int i = 0; i < n; ++i) {
        cin >> ids[i];
    }
    
    if (n == 0) {
        cout << endl;
        return 0;
    }
    
    vector<char> circle;
    circle.push_back(ids[0]);
    int pre_pos = 0; // 前一个人的入座位置
    
    for (int i = 1; i < n; ++i) {
        int cur_size = circle.size();
        // 计算逆时针数m人的目标位置(前一人为第1人)
        int target_pos = (pre_pos + m - 1) % cur_size;
        // 紧邻右侧插入,即目标位置+1
        int insert_pos = target_pos + 1;
        circle.insert(circle.begin() + insert_pos, ids[i]);
        pre_pos = insert_pos; // 更新前一人位置为当前插入位置
    }
    
    // 从最后一人开始顺时针输出面试顺序
    vector<int> res;
    int last_pos = pre_pos;
    for (int i = 0; i < n; ++i) {
        res.push_back(circle[last_pos]);
        last_pos = (last_pos - 1 + n) % n; // 加n避免负数取模错误
    }
    
    // 按格式输出结果
    for (int i = 0; i < res.size(); ++i) {
        if (i > 0) cout << " ";
        cout << res[i];
    }
    cout << endl;
    
    return 0;
}

输入输出示例

示例1(基础场景,n=3,m=2)

输入

3 2
10 20 30

输出

30 20 10

示例2(特殊场景,m大于当前入座人数,n=5,m=6)

输入

5 6
1 2 3 4 5

输出

3 5 4 2 1

示例3 (n=5)

输入

5 3
A B C D E

输出

E A C D B

二、CST 1-2-1 Gift(礼物方案数)

题目描述

小Z是你的好朋友,不久之后将是他的 n 岁生日,你想要为他准备一份惊喜。这是你为小Z过的第一次生日,不满足于只准备一份礼物,你想要为小Z之前的每一次生日,也就是1周岁、2周岁、…、n-1周岁和这次的n周岁生日各准备一份礼物。
在冥思苦想了若干天后,你产生了足量的礼物灵感;又经过了若干天的考量后,你将每份礼物的候选方案筛到了2个。也就是说,对于小Z的第i周岁生日,你已经确定了两个候选的礼物,它们的价格分别为 (c_{i}^{1}) , (c_{i}^{2})。你最终会从这两个礼物中挑选恰好1个送给小Z。
你用于购买这 n 份礼物的总预算为 (P)。你想要知道,一共有多少种礼物方案的总花费不超过 (P)。不难理解,每一个礼物方案,都可唯一地表示为一个序列 (j_{1}, j_{2}, ..., j_{n})((j_i=1或2)),亦即,在该方案中的第 i 份礼物选择了价格为 (c_{i}^{j_{i}}) 者。

输入格式
第一行包含两个正整数,即 n 和 (P)。
接下来 n 行各有两个正整数,依次给出 (c_{i}^{1}) , (c_{i}^{2})。

输出格式
仅一行,仅一个非负整数,即总花费不超过预算 P 的礼物方案数。

数据范围
对于50%的数据,(n ≤20)。另有20%的数据,满足 (0<c_{i}^{1}) , (c_{i}^{2}) , (P ≤10^{6})。对于100%的数据,(n ≤40) , (0<c_{i}^{1}) , (c_{i}^{2}) , (P ≤10^{9})。

资源限制
时间限制:0.6s
空间限制:256MB

解题思路

核心采用折半搜索(Meet-in-the-Middle),解决n≤40时暴力枚举2^40种方案超时的问题:

  1. 将n个礼物平均分割为左右两半(每半≤20个),分别通过深度优先搜索(DFS)枚举两半的所有组合花费,得到两个花费数组;
  2. 对右半部分的花费数组进行排序,为后续二分查找做准备;
  3. 遍历左半部分的每个花费值,计算剩余预算P - 左花费,通过二分查找快速统计右半部分中≤剩余预算的花费数量;
  4. 累加所有符合条件的数量,即为总方案数,时间复杂度优化为(O(2^(n/2) * log2^(n/2))),可在0.6s内通过所有数据。

C++ 完整代码

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

typedef long long ll; // 防止花费累加溢出(P≤1e9,n=40时和最大4e10)

// DFS枚举某一半的所有组合花费
void dfs(int idx, ll sum, const vector<pair<ll, ll>>& cost, vector<ll>& res) {
    if (idx == cost.size()) {
        res.push_back(sum);
        return;
    }
    // 选择第1个礼物
    dfs(idx + 1, sum + cost[idx].first, cost, res);
    // 选择第2个礼物
    dfs(idx + 1, sum + cost[idx].second, cost, res);
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    int n;
    ll P;
    cin >> n >> P;
    vector<pair<ll, ll>> cost(n);
    for (int i = 0; i < n; ++i) {
        cin >> cost[i].first >> cost[i].second;
    }
    
    // 折半分割为左右两部分
    int mid = n / 2;
    vector<pair<ll, ll>> left(cost.begin(), cost.begin() + mid);
    vector<pair<ll, ll>> right(cost.begin() + mid, cost.end());
    
    vector<ll> left_sum, right_sum;
    dfs(0, 0, left, left_sum);
    dfs(0, 0, right, right_sum);
    
    // 右半部分排序,用于二分查找
    sort(right_sum.begin(), right_sum.end());
    
    ll ans = 0;
    for (ll s : left_sum) {
        if (s > P) continue; // 左花费超预算,直接跳过
        ll rest = P - s;
        // 二分找右半部分中≤rest的元素个数
        ans += upper_bound(right_sum.begin(), right_sum.end(), rest) - right_sum.begin();
    }
    
    cout << ans << endl;
    
    return 0;
}

输入输出示例

示例1(基础场景,n=3,P=10)

输入

3 10
2 3
4 5
1 2

输出

8

示例2(边界场景,部分组合超预算,n=4,P=15)

输入

4 15
5 6
3 4
2 1
4 5

输出

12

三、CST 1-2-2 Graphics(线段交点计数)

题目描述

在平面直角坐标系中,如果在x正半轴和y正半轴上分别挑选n个随机点,那么不难理解,只有一种方案能将这两组点一一接起来,使得形成的n条线段互不相交。那么接下来,对第一象限内的任何一点 (P(x, y)),联接原点O与P的线段OP会与这n条线段有多少个交点呢?

输入
第1行包含一个正整数n,即随机点对的数目;
第2行包含n个正整数,即在x正半轴上n个点的横坐标;
第3行包含n个正整数,即在y正半轴上n个点的纵坐标;
第4行包含一个正整数m,即接下来查询的次数;
随后的m行,各包含两个正整数 (x_{i}) 和 (y_i),即各查询点 (P_{i}) 的横、纵坐标。

输出
共m行,各包含一个非负整数,依次列出各次查询的答案。

解题思路

  1. 无交线段配对规则:要让x正半轴和y正半轴的n个点两两连接的线段互不相交,需将x轴点升序排序y轴点降序排序后一一配对(唯一无交方案);
  2. 几何判交转化:利用交叉积避免浮点误差,射线OP与线段(x_i,0)-(0,y_i)相交的充要条件为:(x_p * y_i < x_i * y_p);
  3. 二分计数优化:将配对后的(x_i,y_i)x_i/y_i升序排序(用交叉积比较,避免浮点),对每个查询点(x_p,y_p),通过二分查找快速统计满足相交条件的线段数量;
  4. 整体时间复杂度为(O(n logn + m logn)),可高效处理多组查询。

C++ 完整代码

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

typedef long long ll; // 防止交叉积计算溢出(坐标值较大时)

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    int n;
    cin >> n;
    vector<ll> x(n), y(n);
    for (int i = 0; i < n; ++i) cin >> x[i];
    for (int i = 0; i < n; ++i) cin >> y[i];
    
    // 记录原始索引,用于后续无交配对
    vector<pair<ll, int>> x_idx, y_idx;
    for (int i = 0; i < n; ++i) x_idx.emplace_back(x[i], i);
    for (int i = 0; i < n; ++i) y_idx.emplace_back(y[i], i);
    
    // x轴点升序排序,y轴点降序排序(无交配对规则)
    sort(x_idx.begin(), x_idx.end());
    sort(y_idx.begin(), y_idx.end(), greater<pair<ll, int>>());
    
    // 生成无交的线段配对(xi, yi)
    vector<pair<ll, ll>> pairs;
    for (int i = 0; i < n; ++i) {
        ll xi = x[x_idx[i].second];
        ll yi = y[y_idx[i].second];
        pairs.emplace_back(xi, yi);
    }
    
    // 按xi/yi升序排序(用交叉积比较,避免浮点运算误差)
    sort(pairs.begin(), pairs.end(), [](const pair<ll, ll>& a, const pair<ll, ll>& b) {
        return a.first * b.second < b.first * a.second;
    });
    
    // 处理m次查询
    int m;
    cin >> m;
    while (m--) {
        ll xp, yp;
        cin >> xp >> yp;
        // 二分查找第一个满足xi*yp > xp*yi的位置,该位置即为相交的线段数
        int cnt = lower_bound(pairs.begin(), pairs.end(), make_pair(0LL, 0LL), [&](const pair<ll, ll>& p, const pair<ll, ll>&) {
            return p.first * yp > xp * p.second;
        }) - pairs.begin();
        cout << cnt << endl;
    }
    
    return 0;
}

输入输出示例

示例1(基础场景,n=3,m=2)

输入

3
4 5 3
3 5 4
2
1 1
3 3

输出

1
1

示例2(特殊场景,无交点/全交点,n=2,m=2)

输入

2
2 4
6 3
2
1 10
10 1

输出

0
2

四、CST 1-2-3 二维偏序(三元组最大和)

题目描述

给定n个三元组 ({< a_{i}, b_{i}, c_{i}> | 1 ≤i ≤n}),我们希望从中选出 k 个并按 ({i_{j} | 1 ≤j ≤k}) 排成序列,使得当 (j<k) 时总有 (a_{i_{j}} ≤a_{i_{j+1}}) 且 (b_{i_{j}} ≤b_{i_{j+1}}),并使c的总和取到最大。

输入格式
第一行仅一个正整数 n ,即三元组的总数。
接下来的 n 行各用三个整数 a b 和 c 逐一列出各三元组。

输出格式
仅一行,仅一个整数,即题中所求的最大总和。

解题思路

核心为二维偏序+带权最长上升子序列(LIS)+树状数组优化DP,将二维偏序问题转化为一维问题求解:

  1. 排序消去一维偏序:将所有三元组按a升序排序,若a相同则按b升序排序,此时只需保证b的非递减性即可满足二维偏序条件;
  2. b值离散化:由于b可能为负数、大数或不连续,将b值映射为连续的正整数,适配树状数组的下标要求;
  3. 树状数组优化DP:定义dp[i]为以第i个三元组结尾的最大c和,状态转移为dp[i] = c[i] + max{dp[j] | b[j] ≤ b[i]};利用维护区间最大值的树状数组,实现O(logn)的查询(找前序最大dp值)和更新(更新当前dp值);
  4. 最终答案为所有dp[i]中的最大值,整体时间复杂度为(O(n logn))。

C++ 完整代码

#include <iostream>
#include <vector>
#include <algorithm>
#include <tuple>
using namespace std;

typedef long long ll;
const int MAXN = 1e5 + 5;

// 树状数组:维护区间[1, idx]的最大值
struct FenwickTree {
    vector<ll> tree;
    int n;
    
    // 初始化树状数组,大小为size
    FenwickTree(int size) : n(size) {
        tree.assign(n + 2, 0); // 初始值为0,无三元组时和为0
    }
    
    // 更新idx位置的最大值为val
    void update(int idx, ll val) {
        for (; idx <= n; idx += idx & -idx) {
            if (tree[idx] < val) tree[idx] = val;
            else break; // 无更大值,无需继续更新,优化时间
        }
    }
    
    // 查询区间[1, idx]的最大值
    ll query(int idx) {
        ll res = 0;
        for (; idx > 0; idx -= idx & -idx) {
            if (tree[idx] > res) res = tree[idx];
        }
        return res;
    }
};

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    int n;
    cin >> n;
    vector<tuple<int, int, ll>> tri(n); // 存储三元组(a, b, c)
    vector<int> b_list; // 存储所有b值,用于离散化
    for (int i = 0; i < n; ++i) {
        int a, b;
        ll c;
        cin >> a >> b >> c;
        tri[i] = {a, b, c};
        b_list.push_back(b);
    }
    
    // 步骤1:按a升序排序,a相同则按b升序排序,消去一维偏序
    sort(tri.begin(), tri.end(), [](const tuple<int, int, ll>& t1, const tuple<int, int, ll>& t2) {
        if (get<0>(t1) != get<0>(t2)) return get<0>(t1) < get<0>(t2);
        return get<1>(t1) < get<1>(t2);
    });
    
    // 步骤2:对b值进行离散化(去重+排序+映射)
    sort(b_list.begin(), b_list.end());
    b_list.erase(unique(b_list.begin(), b_list.end()), b_list.end());
    int max_id = b_list.size(); // 离散化后的最大下标
    
    // 步骤3:树状数组优化DP求解最大和
    FenwickTree ft(max_id);
    ll ans = 0;
    for (auto& t : tri) {
        int a = get<0>(t), b = get<1>(t);
        ll c = get<2>(t);
        // 查找b对应的离散化下标(从1开始)
        int bid = lower_bound(b_list.begin(), b_list.end(), b) - b_list.begin() + 1;
        // 查询b[j] ≤ b[i]的前序最大dp值
        ll pre_max = ft.query(bid);
        ll current = pre_max + c; // 当前三元组的dp值
        // 更新树状数组
        ft.update(bid, current);
        // 更新全局最大和
        if (current > ans) ans = current;
    }
    
    cout << ans << endl;
    
    return 0;
}

输入输出示例

示例1(题目样例,n=3)

输入

3
-1 1 2
4 3 1
2 -1 3

输出

4

示例2(多三元组+相同a值,n=4)

输入

4
2 5 3
2 3 5
5 6 4
7 7 6

输出

15

通用编译与运行说明

  1. 所有代码均开启ios::sync_with_stdio(false); cin.tie(nullptr);加速输入输出,适配时间复杂度限制;
  2. 采用long long类型处理所有数值计算,避免整数溢出,覆盖题目1e9/1e10的数值范围;
  3. 编译命令:g++ 文件名.cpp -o 文件名 -O2-O2开启编译器优化,进一步提升运行速度);
posted @ 2026-03-02 16:05  kkman2000  阅读(4)  评论(0)    收藏  举报