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人右侧就座;所有应聘者到齐后,从最后到达者出发,绕圆桌以顺时针方向为序进行面试。
这里假定应聘者到达的时刻互异,且相对的就坐位置确定后,左、右两人之间总能插入一把椅子。
试编写一个程序,确定面试顺序。

输入
共2行。
第1行包含两个整数,n和m。
第2行包含n个整数,表示先后到达的n个应聘者的ID。
输出
共1行。以空格分隔的n个整数,分别表示顺次进行面试的应聘者的ID。
解题思路
- 第一个应聘者直接入列,后续应聘者从前一人位置逆时针数
m人,在第m人右侧插入新位置; - 利用动态数组模拟环形圆桌结构,通过取模运算计算环形位置,避免数组越界;
- 所有应聘者入座后,从最后入座者的位置开始,顺时针遍历(位置逐次减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种方案超时的问题:
- 将n个礼物平均分割为左右两半(每半≤20个),分别通过深度优先搜索(DFS)枚举两半的所有组合花费,得到两个花费数组;
- 对右半部分的花费数组进行排序,为后续二分查找做准备;
- 遍历左半部分的每个花费值,计算剩余预算
P - 左花费,通过二分查找快速统计右半部分中≤剩余预算的花费数量; - 累加所有符合条件的数量,即为总方案数,时间复杂度优化为(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行,各包含一个非负整数,依次列出各次查询的答案。
解题思路
- 无交线段配对规则:要让x正半轴和y正半轴的n个点两两连接的线段互不相交,需将x轴点升序排序、y轴点降序排序后一一配对(唯一无交方案);
- 几何判交转化:利用交叉积避免浮点误差,射线
OP与线段(x_i,0)-(0,y_i)相交的充要条件为:(x_p * y_i < x_i * y_p); - 二分计数优化:将配对后的
(x_i,y_i)按x_i/y_i升序排序(用交叉积比较,避免浮点),对每个查询点(x_p,y_p),通过二分查找快速统计满足相交条件的线段数量; - 整体时间复杂度为(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,将二维偏序问题转化为一维问题求解:
- 排序消去一维偏序:将所有三元组按
a升序排序,若a相同则按b升序排序,此时只需保证b的非递减性即可满足二维偏序条件; - b值离散化:由于
b可能为负数、大数或不连续,将b值映射为连续的正整数,适配树状数组的下标要求; - 树状数组优化DP:定义
dp[i]为以第i个三元组结尾的最大c和,状态转移为dp[i] = c[i] + max{dp[j] | b[j] ≤ b[i]};利用维护区间最大值的树状数组,实现O(logn)的查询(找前序最大dp值)和更新(更新当前dp值); - 最终答案为所有
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
通用编译与运行说明
- 所有代码均开启
ios::sync_with_stdio(false); cin.tie(nullptr);,加速输入输出,适配时间复杂度限制; - 采用
long long类型处理所有数值计算,避免整数溢出,覆盖题目1e9/1e10的数值范围; - 编译命令:
g++ 文件名.cpp -o 文件名 -O2(-O2开启编译器优化,进一步提升运行速度);

浙公网安备 33010602011771号