目录与题目汇总
| 题号 | 题目名称 | 核心考点与算法 | CodeForces 原题链接 |
|---|---|---|---|
| A | Water Buying | 贪心、数学 | CF 1118A |
| B | Edge Deletion | 图论、Dijkstra 最短路树 | CF 1076D |
| C | Basketball Exercise | 线性 DP | CF 1195C |
| D | Tree Painting | 换根 DP | CF 1187E |
| E | LCM | 数论(因数分解) | CF 1068B |
| F | Easy Problem | 状态机线性 DP | CF 1096D |
| G | Catch Overflow! | 栈模拟循环 | CF 1175B |
| H | Magic Ship | 二分答案 + 曼哈顿距离 | CF 1117C |
| I | Maximal Continuous Rest | 环形数组断环成链 | CF 1141B |
| J | Cow and Snacks | 图论生成树、并查集 | CF 1209D |
| K | Array and Segments (Hard version) | 线段树扫描线 | CF 1108E2 |
| L | Vasya and Triangle | 数论与几何构造 | CF 1030D |
| M | Two Arrays and Sum of Functions | 贡献法 + 排序不等式 | CF 1165E |
A. Water Buying (CodeForces 1118A)
- 标签:贪心、数学
题目大意
需要购买 升水。1 升装售价 ,2 升装售价 。求最少需要花费多少钱。
算法思路
比较两种购买方式的单价性价比:
- 买两瓶 1 升装需要花费 。
- 若 ,说明 1 升装更划算或价格相同,全部购买 1 升装即可,花费为 。
- 若 ,说明 2 升装更划算。应尽可能多买 2 升装,即购买 瓶 2 升装;若 为奇数,剩下的 1 升补买 1 瓶 1 升装,总花费为:
综合两式,直接取最小值即可:
复杂度分析
- 时间复杂度:。
- 空间复杂度:。
参考代码
#include <bits/stdc++.h>using namespace std;#define int long long
void solve() { int n, a, b; cin >> n >> a >> b; int x = n * a; int y = (n % 2) * a + (n / 2) * b; cout << min(x, y) << "\n";}
signed main() { ios::sync_with_stdio(false); cin.tie(nullptr); int t; cin >> t; while (t--) { solve(); } return 0;}B. Edge Deletion (CodeForces 1076D)
- 标签:图论、Dijkstra、最短路树 (SPT)、贪心
题目大意
给定包含 个点、 条边的带正权连通无向图。现在需要保留至多 条边。 定义“好点”:如果某个节点在保留边构成的新图中的最短路(以 1 为源点)与原图完全相等,则称该点为好点。 要求最大化好点的数量,输出保留的边数及保留边的编号。
算法思路
- 从源点 1 出发的所有单源最短路径构成一棵最短路树(Shortest Path Tree, SPT),最多包含 条边。
- 观察性质:只要一个点 到根节点 1 的路径上的所有树边都被保留, 在新图中的最短距离就一定与原图一致,因此 成为好点。
- 任何一条保留的边最多只能将 1 个新的好点连接到以 1 为根的连通分量中。因此保留 条边最多产生 个好点。
- 我们在运行 Dijkstra 算法时,当一个点 被从优先队列中取出(距离确定)时,记录将其松弛的树边
parent_edge[u]。 - 按照节点被确定的先后顺序(即距离递增的拓扑/BFS顺序),依次选取前 条树边。因为边权为正,后确定的节点的祖先必已确定,这样保证了被选取的边始终构成包含根节点 1 的一棵连通子树。
复杂度分析
- 时间复杂度:。
- 空间复杂度:。
参考代码
#include <bits/stdc++.h>using namespace std;
struct Edge { int to; long long w; int id;};
int main() { ios::sync_with_stdio(false); cin.tie(nullptr);
int n, m, k; if (!(cin >> n >> m >> k)) return 0;
vector<vector<Edge>> adj(n + 1); for (int i = 1; i <= m; i++) { int u, v; long long w; cin >> u >> v >> w; adj[u].push_back({v, w, i}); adj[v].push_back({u, w, i}); }
const long long INF = 1e18; vector<long long> dist(n + 1, INF); vector<int> parent_edge(n + 1, 0); vector<bool> vis(n + 1, false);
priority_queue<pair<long long, int>, vector<pair<long long, int>>, greater<pair<long long, int>>> pq; dist[1] = 0; pq.push({0, 1});
vector<int> ans;
while (!pq.empty()) { auto [d, u] = pq.top(); pq.pop();
if (vis[u]) continue; vis[u] = true;
if (u != 1 && (int)ans.size() < k) { ans.push_back(parent_edge[u]); }
for (const auto& edge : adj[u]) { int v = edge.to; if (d + edge.w < dist[v]) { dist[v] = d + edge.w; parent_edge[v] = edge.id; pq.push({dist[v], v}); } } }
cout << ans.size() << "\n"; for (int i = 0; i < (int)ans.size(); i++) { cout << ans[i] << (i + 1 == (int)ans.size() ? "" : " "); } cout << "\n";
return 0;}C. Basketball Exercise (CodeForces 1195C)
- 标签:动态规划、线性 DP
题目大意
两排学生,每排各 人。第 1 排学生身高为 ,第 2 排学生身高为 。 要求挑选若干学生组成篮球队,选出的学生列号必须严格单调递增,且队伍所在的行必须严格交替。求选出学生的身高总和最大值。
算法思路
定义状态:
- :在前 列中选人,且第 列选第 1 排的最大身高总和;
- :在前 列中选人,且第 列选第 2 排的最大身高总和。
转移方程:
最终答案为 。
复杂度分析
- 时间复杂度:。
- 空间复杂度:。
参考代码
#include <bits/stdc++.h>#define int long longusing namespace std;
int f[100005][2], a[100005][2];
signed main() { ios::sync_with_stdio(false); cin.tie(nullptr);
int n; if (!(cin >> n)) return 0;
for (int i = 1; i <= n; i++) cin >> a[i][0]; for (int i = 1; i <= n; i++) cin >> a[i][1];
f[1][0] = a[1][0]; f[1][1] = a[1][1];
for (int i = 2; i <= n; i++) { for (int j = 0; j <= 1; j++) { f[i][j] = max(f[i - 1][1 - j], f[i - 2][1 - j]) + a[i][j]; } }
cout << max({f[n - 1][0], f[n - 1][1], f[n][0], f[n][1]}) << "\n"; return 0;}D. Tree Painting (CodeForces 1187E)
- 标签:树形 DP、换根 DP
题目大意
给定包含 个节点的无向树,初始所有点均为白色。
- 第 1 步:任选一个白点染黑,得分加上该点所在的白色连通块大小(初始为 )。
- 第 步():选取一个与已有黑点相邻的白点染黑,得分加上该点染色前所在白色连通块的大小。 求将全部节点染黑所能获得的最大总得分。
算法思路
- 当以 为初始根节点时,总得分为所有节点以 为根时的子树大小之和:
- 换根 DP 转移:
- 第一次 DFS:选 1 为根,统计子树大小 以及 。
- 第二次 DFS:根由 移动到子节点 :
- 遍历所有节点求 。
复杂度分析
- 时间复杂度:。
- 空间复杂度:。
参考代码
#include <bits/stdc++.h>using namespace std;
int main() { ios::sync_with_stdio(false); cin.tie(nullptr);
int n; if (!(cin >> n)) return 0;
vector<vector<int>> adj(n + 1); for (int i = 0; i < n - 1; i++) { int u, v; cin >> u >> v; adj[u].push_back(v); adj[v].push_back(u); }
vector<long long> sz(n + 1, 0); vector<long long> dp(n + 1, 0);
auto dfs1 = [&](auto& self, int u, int p) -> void { sz[u] = 1; for (int v : adj[u]) { if (v == p) continue; self(self, v, u); sz[u] += sz[v]; } dp[1] += sz[u]; };
dfs1(dfs1, 1, 0);
long long max_ans = dp[1];
auto dfs2 = [&](auto& self, int u, int p) -> void { for (int v : adj[u]) { if (v == p) continue; dp[v] = dp[u] - sz[v] + (n - sz[v]); max_ans = max(max_ans, dp[v]); self(self, v, u); } };
dfs2(dfs2, 1, 0);
cout << max_ans << "\n"; return 0;}E. LCM (CodeForces 1068B)
- 标签:数论、因数分解
题目大意
给定正整数 (),对于所有正整数 ,求 的不同取值个数。
算法思路
由公式: 由于 可以取遍 的每一个正因数,因此不同取值的个数严格等于 的因数个数 。在 内枚举因数即可。
复杂度分析
- 时间复杂度:。
- 空间复杂度:。
参考代码
#include <bits/stdc++.h>#define int long longusing namespace std;
signed main() { int b; if (cin >> b) { int ans = 0; for (int i = 1; i * i <= b; ++i) { if (b % i == 0) { ans += (i * i == b ? 1 : 2); } } cout << ans << "\n"; } return 0;}F. Easy Problem (CodeForces 1096D)
- 标签:动态规划、字符串
题目大意
给定长度为 的字符串 。删除第 个字符的花费为 。求最小的花费,使得剩余的字符串中不包含子序列 "hard"。
算法思路
维护前缀串消除各个前缀子序列的代价:
- :无子序列
"h"; - :无子序列
"ha"; - :无子序列
"har"; - :无子序列
"hard"。
转移:
- :;
- :;
- :;
- :。
答案为 。
复杂度分析
- 时间复杂度:。
- 空间复杂度:。
参考代码
#include <bits/stdc++.h>using namespace std;
int main() { ios::sync_with_stdio(false); cin.tie(nullptr);
int n; if (!(cin >> n)) return 0;
string s; cin >> s;
vector<long long> a(n); for (int i = 0; i < n; i++) cin >> a[i];
vector<long long> dp(4, 0);
for (int i = 0; i < n; i++) { if (s[i] == 'h') { dp[0] += a[i]; } else if (s[i] == 'a') { dp[1] = min(dp[1] + a[i], dp[0]); } else if (s[i] == 'r') { dp[2] = min(dp[2] + a[i], dp[1]); } else if (s[i] == 'd') { dp[3] = min(dp[3] + a[i], dp[2]); } }
cout << dp[3] << "\n"; return 0;}G. Catch Overflow! (CodeForces 1175B)
- 标签:栈模拟、大数防溢出
题目大意
初始 。程序包含 for n、end、add。add 会累加外层循环的连乘倍数。若 输出 OVERFLOW!!!,否则输出最终的 。
算法思路
使用栈维护循环倍数的前缀积。遇到乘积超过 时截断为 防止越界。执行 add 时累加,若 立即输出 OVERFLOW!!!。
复杂度分析
- 时间复杂度:。
- 空间复杂度:。
参考代码
#include <bits/stdc++.h>#define int long longusing namespace std;
const int max_ = 1LL << 32;
signed main() { ios::sync_with_stdio(false); cin.tie(nullptr);
int l; if (!(cin >> l)) return 0;
vector<int> stk; stk.reserve(l + 1); stk.push_back(1);
int ans = 0; for (int i = 0; i < l; i++) { string s; cin >> s; if (s == "for") { int n; cin >> n; stk.push_back(min(max_, stk.back() * n)); } else if (s == "end") { stk.pop_back(); } else if (s == "add") { ans += stk.back(); if (ans >= max_) { cout << "OVERFLOW!!!\n"; return 0; } } }
cout << ans << "\n"; return 0;}H. Magic Ship (CodeForces 1117C)
- 标签:二分答案、前缀和、曼哈顿距离
题目大意
起点 ,终点 。风力周期为 。每天风力推船 1 步,船自主可走 1 步或不动。求最少到达天数;无法到达输出 -1。
算法思路
二分天数 。计算前 天风力位移 ,风力吹到点为 。 可行充要条件: 二分上界取 。
复杂度分析
- 时间复杂度:。
- 空间复杂度:。
参考代码
#include <bits/stdc++.h>#define int long longusing namespace std;
signed main() { ios::sync_with_stdio(false); cin.tie(nullptr);
int x1, y1, x2, y2; if (!(cin >> x1 >> y1 >> x2 >> y2)) return 0;
int n; string s; cin >> n >> s;
if (x1 == x2 && y1 == y2) { cout << 0 << "\n"; return 0; }
vector<int> px(n + 1, 0), py(n + 1, 0); for (int i = 0; i < n; i++) { px[i + 1] = px[i]; py[i + 1] = py[i]; if (s[i] == 'U') py[i + 1]++; else if (s[i] == 'D') py[i + 1]--; else if (s[i] == 'L') px[i + 1]--; else if (s[i] == 'R') px[i + 1]++; }
int l = 1, r = 1e15; int ans = -1;
auto check = [&](int mid) -> bool { int cnt = mid / n, rem = mid % n; int wx = cnt * px[n] + px[rem], wy = cnt * py[n] + py[rem]; int cur_x = x1 + wx, cur_y = y1 + wy; return mid >= llabs(x2 - cur_x) + llabs(y2 - cur_y); };
while (l <= r) { int mid = l + (r - l) / 2; if (check(mid)) { ans = mid; r = mid - 1; } else { l = mid + 1; } }
cout << ans << "\n"; return 0;}I. Maximal Continuous Rest (CodeForces 1141B)
- 标签:环形数组、断环成链
题目大意
给定循环 0/1 数组,求最长连续 1 的长度。
算法思路
数组倍长为 ,统计最大连续 1 计数,与 取 。
复杂度分析
- 时间复杂度:。
- 空间复杂度:。
参考代码
#include <bits/stdc++.h>using namespace std;
int main() { ios::sync_with_stdio(false); cin.tie(nullptr);
int n; if (!(cin >> n)) return 0;
vector<int> a(2 * n); for (int i = 0; i < n; i++) { cin >> a[i]; a[i + n] = a[i]; }
int ans = 0, cur = 0; for (int i = 0; i < 2 * n; i++) { if (a[i] == 1) { cur++; ans = max(ans, cur); } else { cur = 0; } }
ans = min(ans, n); cout << ans << "\n"; return 0;}J. Cow and Snacks (CodeForces 1209D)
- 标签:图论、连通分量、并查集
题目大意
种零食, 位客人。每人喜好两种零食,若都已被拿走则伤心。求最少伤心人数。
算法思路
大小为 的连通块最多满足 人。使用并查集合并,每次成功合并说明增加 1 位满意客人。答案为 。
复杂度分析
- 时间复杂度:。
- 空间复杂度:。
参考代码
#include <bits/stdc++.h>using namespace std;
struct DSU { vector<int> parent; DSU(int n) : parent(n + 1) { iota(parent.begin(), parent.end(), 0); } int find(int i) { if (parent[i] == i) return i; return parent[i] = find(parent[i]); } bool unite(int i, int j) { int root_i = find(i); int root_j = find(j); if (root_i != root_j) { parent[root_i] = root_j; return true; } return false; }};
int main() { ios::sync_with_stdio(false); cin.tie(nullptr);
int n, k; if (!(cin >> n >> k)) return 0;
DSU dsu(n); int happy = 0;
for (int i = 0; i < k; i++) { int x, y; cin >> x >> y; if (dsu.unite(x, y)) { happy++; } }
int sad = k - happy; cout << sad << "\n"; return 0;}K. Array and Segments (Hard version) (CodeForces 1108E2)
- 标签:线段树、扫描线、贪心
题目大意
选择区间子集执行 ,最大化 。
算法思路
若最小值在 取得,最优策略是选择全部覆盖 的区间。扫描线遍历 ,在 时线段树区间 ,在 之后区间 ,全局维护 。
复杂度分析
- 时间复杂度:。
- 空间复杂度:。
参考代码
#include <bits/stdc++.h>using namespace std;
struct Node { int mn, mx, lazy;};
const int INF = 1e9;vector<Node> tree;vector<int> a;
void push_up(int u) { tree[u].mn = min(tree[u << 1].mn, tree[u << 1 | 1].mn); tree[u].mx = max(tree[u << 1].mx, tree[u << 1 | 1].mx);}
void apply(int u, int val) { tree[u].mn += val; tree[u].mx += val; tree[u].lazy += val;}
void push_down(int u) { if (tree[u].lazy != 0) { apply(u << 1, tree[u].lazy); apply(u << 1 | 1, tree[u].lazy); tree[u].lazy = 0; }}
void build(int u, int l, int r) { tree[u].lazy = 0; if (l == r) { tree[u].mn = a[l]; tree[u].mx = a[l]; return; } int mid = l + (r - l) / 2; build(u << 1, l, mid); build(u << 1 | 1, mid + 1, r); push_up(u);}
void update(int u, int l, int r, int ql, int qr, int val) { if (ql <= l && r <= qr) { apply(u, val); return; } push_down(u); int mid = l + (r - l) / 2; if (ql <= mid) update(u << 1, l, mid, ql, qr, val); if (qr > mid) update(u << 1 | 1, mid + 1, r, ql, qr, val); push_up(u);}
struct Segment { int l, r, id;};
int main() { ios::sync_with_stdio(false); cin.tie(nullptr);
int n, m; if (!(cin >> n >> m)) return 0;
a.resize(n + 1); int orig_min = INF, orig_max = -INF; for (int i = 1; i <= n; i++) { cin >> a[i]; orig_min = min(orig_min, a[i]); orig_max = max(orig_max, a[i]); }
vector<Segment> segs(m); vector<vector<int>> start_at(n + 2), end_at(n + 2); for (int i = 0; i < m; i++) { cin >> segs[i].l >> segs[i].r; segs[i].id = i + 1; start_at[segs[i].l].push_back(i); end_at[segs[i].r].push_back(i); }
tree.resize(4 * (n + 1)); build(1, 1, n);
int best_diff = orig_max - orig_min; int best_p = 0;
for (int p = 1; p <= n; p++) { for (int idx : start_at[p]) { update(1, 1, n, segs[idx].l, segs[idx].r, -1); }
int cur_diff = tree[1].mx - tree[1].mn; if (cur_diff > best_diff) { best_diff = cur_diff; best_p = p; }
for (int idx : end_at[p]) { update(1, 1, n, segs[idx].l, segs[idx].r, 1); } }
vector<int> chosen; if (best_p > 0) { for (int i = 0; i < m; i++) { if (segs[i].l <= best_p && best_p <= segs[i].r) { chosen.push_back(segs[i].id); } } }
cout << best_diff << "\n"; cout << chosen.size() << "\n"; for (int i = 0; i < (int)chosen.size(); i++) { cout << chosen[i] << (i + 1 == (int)chosen.size() ? "" : " "); } cout << "\n";
return 0;}L. Vasya and Triangle (CodeForces 1030D)
- 标签:数论、构造、几何
题目大意
在 区域构造三角形使面积为 。
算法思路
必要条件 。构造直角边 。
- 若 为偶数:,;
- 若 为奇数:求出基础值后将因子 2 乘给不超过边界的一维。
复杂度分析
- 时间复杂度:。
- 空间复杂度:。
参考代码
#include <bits/stdc++.h>#define int long longusing namespace std;
signed main() { ios::sync_with_stdio(false); cin.tie(nullptr);
int n, m, k; if (!(cin >> n >> m >> k)) return 0;
if ((2 * n * m) % k != 0) { cout << "NO\n"; return 0; }
cout << "YES\n"; int x, y; if (k % 2 == 0) { int kp = k / 2; int g = gcd(n, kp); x = n / g; y = m / (kp / g); } else { int g = gcd(n, k); x = n / g; y = m / (k / g); if (2 * x <= n) { x *= 2; } else { y *= 2; } }
cout << "0 0\n"; cout << x << " 0\n"; cout << "0 " << y << "\n";
return 0;}M. Two Arrays and Sum of Functions (CodeForces 1165E)
- 标签:贡献法、排序不等式
题目大意
重排数组 ,求 模 998244353 的最大值。
算法思路
计算贡献 。 排序不等式:将 和 均升序排序后对应相乘。注意排序前切勿取模。
复杂度分析
- 时间复杂度:。
- 空间复杂度:。
参考代码
#include <bits/stdc++.h>using namespace std;
int main() { ios::sync_with_stdio(false); cin.tie(nullptr);
int n; if (!(cin >> n)) return 0;
vector<long long> a(n), b(n); for (int i = 0; i < n; i++) cin >> a[i]; for (int i = 0; i < n; i++) cin >> b[i];
vector<long long> w(n); for (int i = 0; i < n; i++) { w[i] = a[i] * (i + 1LL) * (n - i); }
sort(w.begin(), w.end()); sort(b.begin(), b.end());
const long long MOD = 998244353; long long ans = 0; for (int i = 0; i < n; i++) { long long term = (w[i] % MOD) * (b[i] % MOD) % MOD; ans = (ans + term) % MOD; }
cout << ans << "\n"; return 0;}分享文章
生成精美分享图或复制链接,与更多人分享本文。
继续阅读
换条路线
从其他文章中稳定抽取
最后更新于 ,距今已过 0 天
部分内容可能已过时