CF Educational Round3 个人题解
CF Educational Round 3 个人题解
A. USB Flash Drives
题意
有 \(n\) 个硬盘每个硬盘有 \(a_i\) 的容量,假设你可以把一个 \(m\) 大小的文件分成若干个硬盘内存放,你至少需要几个硬盘
思路
水,直接从大到小排序贪心即可
复杂度瓶颈为排序, \(O(n \ logn)\)
代码
// A. USB Flash Drives
// 2000 ms
// 256 MB
// https://codeforces.com/contest/609/problem/A
#include <bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
using i128 = __int128;
using u128 = unsigned __int128;
using real = long double;
int __stt = clock();
int tests = 1;
void solve() {
int n, m;
std::cin >> n >> m;
std::vector<int> a(n, 0);
for (auto& x : a)
std::cin >> x;
std::sort(a.rbegin(), a.rend());
int cnt = 0;
int sum = 0;
for (const auto& x : a) {
sum += x;
++cnt;
if (sum >= m)
break;
}
std::cout << cnt << '\n';
}
int main() {
std::ios::sync_with_stdio(0);
std::cin.tie(0);
std::cout.tie(0);
while (tests--) {
solve();
}
#ifdef LOCAL
int __edt = clock();
std::cerr << "Time: " << (__edt - __stt) << "ms\n";
#endif
return 0;
}
B. The Best Gift
题意
有 \(n\) 本书, 每本书的类型为 \(1\) 到 \(m\) 中的一个整数, 有多少种选两本书的选法使得这两本书类型不同?
思路
这道题的 \(m\) 好像只有 \(10\), 所以可以枚举选择哪两种类型,但我没有看到,以为 \(m\) 也是 \(1e5\),所以我选择了一个非常 naive 的推式子
我们记有 \(cnt_i\) 个类型为 \(i\) 的书
首先选择的两本书编号一定不同,那我们钦定两本书中编号较大的编号为 \(a\),则这本书有 \(cnt_a\) 种选法,它可以匹配任意一本编号比它小的书
我们用前缀和统计编号小于 \(i\) 的书一共有多少种,记为 \(pre_i\), 然后乘法原理枚举 \(m\) 统计即可
最终答案即为 \(\sum_{i = 1}^{m} cnt_i \times pre_{i - 1}\)
时间复杂度 \(O(n + m) = O(n)\) (其实是比 \(O(n + m ^ 2)\) 要好的对吧QwQ)
代码
// B. The Best Gift
// 2000 ms
// 256 MB
// https://codeforces.com/contest/609/problem/B
#include <bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
using i128 = __int128;
using u128 = unsigned __int128;
using real = long double;
int __stt = clock();
int tests = 1;
void solve() {
#define int i64
int n, m;
std::cin >> n >> m;
std::vector<int> a(n + 1, 0);
for (int i = 1; i <= n; ++i)
std::cin >> a[i];
std::vector<int> cnt(m + 1, 0);
for (int i = 1; i <= n; ++i)
++cnt[a[i]];
std::vector<int> sum(m + 1, 0);
for (int i = 1; i <= m; ++i)
sum[i] = sum[i - 1] + cnt[i];
int ans = 0;
for (int i = 1; i <= m; ++i)
ans += cnt[i] * sum[i - 1];
std::cout << ans << '\n';
#undef int
}
int main() {
std::ios::sync_with_stdio(0);
std::cin.tie(0);
std::cout.tie(0);
while (tests--) {
solve();
}
#ifdef LOCAL
int __edt = clock();
std::cerr << "Time: " << (__edt - __stt) << "ms\n";
#endif
return 0;
}
C. Load Balancing
题意
有 \(n\) 个服务器,每个服务器有 \(m_i\) 的数据,每秒钟你可以选择一对服务器传送 \(1\) 的数据
最少需要多少秒可以使得最大的服务器数据量减去最小的服务器数据量最小?
思路
显然最小的时候要么所有服务器数据量相同要么相差 \(1\)(如果相差 \(2\) 可以让最大的给最小的传输 \(1\))
所以我们可以计算出最后每个服务器的数据
计所有数据总和为 \(sum\),最后每个服务器的数据量就应该为 \(sum / n\) 或 \(sum / n + 1\)
对于小于 \(sum / n\) 的,我们肯定考虑用一个 $ > sum / n$ 数据量的服务器向它传输直至它达到 \(sum / n\),我们先统计出这一部分
但是到最后可能会有一些 $ > sum / n + 1$ 的服务器,这个时候要把它们传输给数据量为 \(sum / n\) 的服务器,这一部分我们也需要统计,记为 \(ext\)
我这里的统计方法是先算出把不足的补齐需要多少,在统计把超过部分减到合法需要多少,多的部分就是 \(ext\)
复杂度 \(O(n)\)
代码
// C. Load Balancing
// 2000 ms
// 256 MB
// https://codeforces.com/contest/609/problem/C
#include <bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
using i128 = __int128;
using u128 = unsigned __int128;
using real = long double;
int __stt = clock();
int tests = 1;
void solve() {
int n;
std::cin >> n;
std::vector<int> m(n + 1, 0);
for (int i = 1; i <= n; ++i)
std::cin >> m[i];
int sum = std::accumulate(m.begin(), m.end(), 0);
int every = sum / n;
int ans = 0;
for (int i = 1; i <= n; ++i)
if (m[i] < every)
ans += every - m[i];
int need = ans, ext = 0;
for (int i = 1; i <= n; ++i) {
if (m[i] > every + 1)
need -= (m[i] - every - 1);
if (need < 0) {
ext -= need;
need = 0;
}
}
std::cout << ans + ext << '\n';
}
int main() {
std::ios::sync_with_stdio(0);
std::cin.tie(0);
std::cout.tie(0);
while (tests--) {
solve();
}
#ifdef LOCAL
int __edt = clock();
std::cerr << "Time: " << (__edt - __stt) << "ms\n";
#endif
return 0;
}
D. Gadgets for dollars and pounds
题意
你有 \(s\) 块钱布朗,有 \(n\) 天,每天会更新一个汇率,第 \(i\) 天换一美元需要 \(a_i\) 布朗,换一英镑需要 \(b_i\) 布朗
现在有 \(m\) 个物品,第 \(i\) 个物品有两个属性 \(t_i\) 和 \(c_i\),如果 \(t_i = 1\) 则这个物品需要 \(c_i\) 美金,如果 \(t_i = 2\) 则这个物品需要 \(c_i\) 英镑
每天可以购买任意个物品,但是每个物品在这 \(n\) 天里只能购买 \(1\) 次
在这 \(n\) 天内你至少需要多少天才可以买到 \(k\) 个物品?如果不能买到输出 \(-1\)
思路
因为我们注意到如果可以在 \(d\) 天完成则一定可以在 $ > \ d $ 天完成,所以这个答案具有可二分性,我们去二分这个答案是一个基本的观察
现在考虑如何 check, 因为一个物品的价值是固定的,但是汇率不固定,所以贪心的想我们在汇率最低的一天去买一定是最优的,分别看美金和英镑的最低汇率然后看 \(k\) 个物品最少需要多少布朗即可
如果最后发现无法购买就输出 \(-1\)
复杂度 \(O(m \ logm \ logn)\)
代码
// D. Gadgets for dollars and pounds
// 2000 ms
// 256 MB
// https://codeforces.com/contest/609/problem/D
#include <bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
using i128 = __int128;
using u128 = unsigned __int128;
using real = long double;
int __stt = clock();
int tests = 1;
void solve() {
int n, m, k, s;
std::cin >> n >> m >> k >> s;
std::vector<int> a(n), b(n);
for (int i = 0; i < n; ++i)
std::cin >> a[i];
for (int i = 0; i < n; ++i)
std::cin >> b[i];
std::vector<int> t(m), c(m);
for (int i = 0; i < m; ++i)
std::cin >> t[i] >> c[i];
std::vector<std::pair<int, int>> preA(n), preB(n);
preA[0] = {a[0], 0};
for (int i = 1; i < n; ++i) {
if (a[i] < preA[i - 1].first)
preA[i] = {a[i], i};
else
preA[i] = preA[i - 1];
}
preB[0] = {b[0], 0};
for (int i = 1; i < n; ++i) {
if (b[i] < preB[i - 1].first)
preB[i] = {b[i], i};
else
preB[i] = preB[i - 1];
}
auto check = [&](int d) -> bool {
int da = preA[d - 1].second;
i64 ra = preA[d - 1].first;
int db = preB[d - 1].second;
i64 rb = preB[d - 1].first;
std::vector<std::pair<i64, int>> tmp(m);
for (int i = 0; i < m; ++i) {
i64 pr = (t[i] == 1) ? (i64)c[i] * ra : (i64)c[i] * rb;
tmp[i] = {pr, i};
}
std::nth_element(tmp.begin(), tmp.begin() + k - 1, tmp.end());
i64 sum = 0;
for (int i = 0; i < k; ++i)
sum += tmp[i].first;
return sum <= s;
};
int l = 1, r = n + 1;
while (l < r) {
int mid = (l + r) / 2;
if (check(mid))
r = mid;
else
l = mid + 1;
}
if (l == n + 1) {
std::cout << -1 << '\n';
return;
}
int d = l;
int da = preA[d - 1].second;
i64 ra = preA[d - 1].first;
int db = preB[d - 1].second;
i64 rb = preB[d - 1].first;
std::vector<std::pair<i64, int>> tmp(m);
for (int i = 0; i < m; ++i) {
i64 pr = (t[i] == 1) ? (i64)c[i] * ra : (i64)c[i] * rb;
tmp[i] = {pr, i};
}
std::nth_element(tmp.begin(), tmp.begin() + k - 1, tmp.end());
std::cout << d << '\n';
for (int i = 0; i < k; ++i) {
int id = tmp[i].second;
int day = (t[id] == 1) ? da + 1 : db + 1;
std::cout << id + 1 << ' ' << day << '\n';
}
}
int main() {
std::ios::sync_with_stdio(0);
std::cin.tie(0);
std::cout.tie(0);
while (tests--) {
solve();
}
#ifdef LOCAL
int __edt = clock();
std::cerr << "Time: " << (__edt - __stt) << "ms\n";
#endif
return 0;
}
E. Minimum spanning tree for each edge
题意
给定一个简单无向图,对于每一条边,求包含这条边的最小生成树的权值
思路
我们先跑一遍最小生成树
我们对于每一条边分两类讨论:
-
如果这条边在该图的最小生成树内,权值就等于这个最小生成树的权值
-
如果这条边不在该图的最小生成树内,那么由树的定义,加入这条边后,原最小生成树就一定会形成一个环,我们可以考虑移去这个环中除了加进来的这条边之外的最大的边,此时我们使用倍增,记加入的边为 \((u, v)\),这个的组成部分即为该最小生成树上 \(LCA(u, v)\) 到 \(u\) 和 \(v\) 的路径 + 边 \((u, v)\)
预处理 LCA 的时候我们也顺便处理一下从节点 \(u\) 出发向上跳 \(2^i\) 个节点的边权的最大值,这一部分其实和 ST 表比较像
所以我们在倍增查找 LCA 的同时也可以得到这两条路径上的最大值,最后的答案即为原最小生成树的权值 - 环上除了 \((u, v)\) 边之外的边权最大值 + \((u, v)\) 边权
复杂度:
预处理 \(O(n \ logm + n \ logn)\) (最小生成树 + 倍增LCA预处理)
答案计算 \(O(m \ logn)\) (每条边查询 LCA)
代码
// E. Minimum spanning tree for each edge
// 2000 ms
// 256 MB
// https://codeforces.com/contest/609/problem/E
#include <bits/stdc++.h>
using i64 = long long;
using u64 = unsigned long long;
using i128 = __int128;
using u128 = unsigned __int128;
using real = long double;
int __stt = clock();
int tests = 1;
void solve() {
int n, m;
std::cin >> n >> m;
std::vector<std::array<int, 4>> ed(m);
for (int i = 0; i < m; ++i) {
int u, v, w;
std::cin >> u >> v >> w;
ed[i] = {u, v, w, i};
}
auto sed = ed;
std::sort(sed.begin(), sed.end(),
[](auto& a, auto& b) { return a[2] < b[2]; });
std::vector<int> dsu(n + 1);
std::iota(dsu.begin(), dsu.end(), 0);
std::function<int(int)> find = [&](int x) -> int {
if (dsu[x] != x)
dsu[x] = find(dsu[x]);
return dsu[x];
};
auto unite = [&](int x, int y) -> bool {
x = find(x), y = find(y);
if (x == y)
return false;
dsu[y] = x;
return true;
};
std::vector<std::vector<std::pair<int, int>>> adj(n + 1);
std::vector<bool> inMST(m, false);
i64 M = 0;
for (auto& e : sed) {
int u = e[0], v = e[1], w = e[2], id = e[3];
if (unite(u, v)) {
inMST[id] = true;
M += w;
adj[u].emplace_back(v, w);
adj[v].emplace_back(u, w);
}
}
int lg = 20;
std::vector<std::vector<int>> up(lg, std::vector<int>(n + 1));
std::vector<std::vector<int>> mx(lg, std::vector<int>(n + 1));
std::vector<int> dep(n + 1);
auto dfs = [&](int u, int p, int w, auto&& self) -> void {
dep[u] = dep[p] + 1;
up[0][u] = p;
mx[0][u] = w;
for (int k = 1; k < lg; ++k) {
up[k][u] = up[k - 1][up[k - 1][u]];
mx[k][u] = std::max(mx[k - 1][u], mx[k - 1][up[k - 1][u]]);
}
for (auto [v, nw] : adj[u]) {
if (v == p)
continue;
self(v, u, nw, self);
}
};
dfs(1, 0, 0, dfs);
auto get = [&](int u, int v) -> int {
if (dep[u] < dep[v])
u ^= v ^= u ^= v;
int dif = dep[u] - dep[v];
int res = 0;
for (int k = 0; k < lg; ++k) {
if (dif bitand (1 << k)) {
res = std::max(res, mx[k][u]);
u = up[k][u];
}
}
if (u == v)
return res;
for (int k = lg - 1; k >= 0; --k) {
if (up[k][u] != up[k][v]) {
res = std::max({res, mx[k][u], mx[k][v]});
u = up[k][u];
v = up[k][v];
}
}
res = std::max({res, mx[0][u], mx[0][v]});
return res;
};
for (int i = 0; i < m; ++i) {
int u = ed[i][0], v = ed[i][1], w = ed[i][2];
if (inMST[i]) {
std::cout << M << '\n';
} else {
int mx = get(u, v);
std::cout << M - mx + w << '\n';
}
}
}
int main() {
std::ios::sync_with_stdio(0);
std::cin.tie(0);
std::cout.tie(0);
while (tests--) {
solve();
}
#ifdef LOCAL
int __edt = clock();
std::cerr << "Time: " << (__edt - __stt) << "ms\n";
#endif
return 0;
}
杂谈
这题做着做着想到了 CSP-S 2025 T2
当时我很快想到了一个复杂度正确的思路,但是在考场三个多小时没有调出来 Kruskal,因为考前也没有专心打板子,打比赛大多板子也是贺的,几乎忘记了正确的 Kruskal 只需要 sort 一下就可以求出来,现场发明了 priority_queue 写 Kruskal 最后 T2 0pts 无缘 NOIP
这是我为什么现在开始刷 Edu 的原因
或许贺板子是我 OI 生涯干过最严重的错误吧
F. Frogs and mosquitoes
不会了,听说可以小常数乱搞,不懂

浙公网安备 33010602011771号