ZZJC 2024 autumn Individual Contest - 11(题解)
预测难度
Easy:A、C
Medium:E、B、F
Hard:D
实际难度
Easy:A、C
Medium:F、B
Hard:D、E
A - Debug
分析
错误存在第7行
s.length()
和s.size()
返回的类型为unsigned long long
,当有符号型整数遇到无符号整数时类型自动转换为无符号,因此i >= s.length() - k
永远无法成立,导致程序陷入死循环。
此处应改为i >= (int)s.length() - k
。
代码实现
#include <bits/stdc++.h>
#ifdef LOCAL
#include "algo/debug.hpp"
#else
#define debug(...) 42
#endif
int main() {
std::cin.tie(nullptr)->sync_with_stdio(false);
std::cout << "Yes" << "\n";
std::cout << 7 << '\n';
}
C - 两数之和
分析
由于\(n \leq 2 * 10^5\),如果进行双重循环遍历时间复杂为\(O(n^2)\),显然会超时。
考虑优化,使用std::muiltiset
进行存储和查询,查询和存取的时间复杂度为\(O(logn)\),对于每个数\(y\)进行遍历,如果std::muiltiset
存在\(x - y\)即输出YES
,遍历结束后不存在即输出NO
。
同理也可以使用二分查找判断\(x - y\)是否存在。
最终的复杂度均为\(O(nlogn)\)
注意判断存在重复的情况。
代码实现(multiset版)
#include <bits/stdc++.h>
#ifdef LOCAL
#include "algo/debug.hpp"
#else
#define debug(...) 42
#endif
int main() {
std::cin.tie(nullptr)->sync_with_stdio(false);
int n, x;
std::cin >> n >> x;
std::vector<int> a(n);
std::multiset<int> st;
for (int i = 0; i < n; ++i) {
std::cin >> a[i];
st.insert(a[i]);
}
for (int i = 0; i < n; ++i) {
if (a[i] * 2 != x) {
if (st.contains(x - a[i])) {
std::cout << "YES" << '\n';
return 0;
}
} else if (st.count(a[i]) > 1) {
std::cout << "YES" << '\n';
return 0;
}
}
std::cout << "NO" << '\n';
}
代码实现(二分版)
#include <bits/stdc++.h>
#ifdef LOCAL
#include "algo/debug.hpp"
#else
#define debug(...) 42
#endif
int main() {
std::cin.tie(nullptr)->sync_with_stdio(false);
int n, x;
std::cin >> n >> x;
std::vector<int> a(n);
for (int i = 0; i < n; ++i) {
std::cin >> a[i];
}
std::sort(a.begin(), a.end());
for (int i = 0; i < n; ++i) {
int pos = std::ranges::lower_bound(a, x - a[i]) - a.begin();
if (pos != n && a[pos] + a[i] == x && pos != i) {
std::cout << "YES" << '\n';
return 0;
}
}
std::cout << "NO" << '\n';
}
E - 排版
老东西看了这题应该很亲切吧,没错他又来了~
分析
本题主要难度在于如何处理字符串,对于若干行可以选择使用while(std::getline(std::cin, line))
进行行读入。
对于空格的处理可以使用std::stringstream
进行重载输入流,将读到的不含空格的字符串存入std::vector<std::string>
数组中,最后按照题意处理即可。
代码实现
#include <bits/stdc++.h>
#ifdef LOCAL
#include "algo/debug.hpp"
#else
#define debug(...) 42
#endif
int main() {
std::cin.tie(nullptr)->sync_with_stdio(false);
std::string line;
while (std::getline(std::cin, line)) {
std::stringstream is(line);
std::vector<std::string> v;
std::string p;
while (is >> p) {
std::transform(p.begin(), p.end(), p.begin(), tolower);
v.push_back(p);
}
for (int i = 0; i < size(v); ++i) {
if (i == 0 || v[i - 1].back() == '.' || v[i] == "i," || v[i] == "i." || v[i] == "i") {
v[i][0] -= 32;
}
std::cout << v[i] << " \n"[i == size(v) - 1];
}
}
}
B - Tree Degree Optimization
分析
贪心。在确保每个点都有入度d(即最终的图为一棵树而非森林)的情况下,每次添加\((d^2-(d-1)^2)*a_i\)即可,一棵数的总度数为\(2 * n - 2\)。
代码实现
#include <bits/stdc++.h>
#ifdef LOCAL
#include "algo/debug.h"
#else
#define debug(...) 42
#endif
int main() {
std::cin.tie(nullptr)->sync_with_stdio(false);
#define int long long
int n;
std::cin >> n;
std::vector<int> a(n);
for (int i = 0; i < n; ++i) {
std::cin >> a[i];
}
int ans = 0;
std::priority_queue<std::pair<int, int>, std::vector<std::pair<int, int>>, std::greater<>> q;
for (int i = 0; i < n; ++i) {
ans += a[i];
q.emplace(3LL * a[i], 2);
}
for (int i = 1; i < n - 1; ++i) {
auto [w, num] = q.top();
q.pop();
ans += w;
q.emplace(w / (2 * num - 1) * (2 * num + 1), num + 1);
}
std::cout << ans << '\n';
}
F - Partition
分析
贪心,对k的大小进行分类讨论即可。
首先将所有的0取出放在最前面,随后进行分类讨论:
当\(k > 0\)时可以发现一定有解,将所有的负数从小到大插入,正数从大到小插入即可;
当\(k = 0\)时可以发现当\(\sum_{i = 1}^{i = n}a_i \ge 0\)时有解,此时将所有的正数从大到小插入,再将负数从小到大插入即可;
当\(k < 0\)时可以发现当\(\sum_{i = 1}^{i = n}a_i \ge k\)时有解,此时将所有的正数从大到小插入,再将负数从小到大插入即可。
代码实现
#include <bits/stdc++.h>
#ifdef LOCAL
#include "algo/debug.h"
#else
#define debug(...) 42
#endif
int main() {
std::cin.tie(nullptr)->sync_with_stdio(false);
#define int long long
int n, k;
std::cin >> n >> k;
std::vector<int> a, b, c;
for (int i = 0; i < n; ++i) {
int x;
std::cin >> x;
if (x == 0) {
c.push_back(x);
} else if (x > 0) {
a.push_back(x);
} else {
b.push_back(x);
}
}
std::sort(a.rbegin(), a.rend()); // +
std::sort(b.begin(), b.end()); // -
int s = std::accumulate(a.begin(), a.end(), 0LL) + std::accumulate(b.begin(), b.end(), 0LL);
if (k == 0) {
if (s >= 0) {
c.insert(c.end(), a.begin(), a.end());
c.insert(c.end(), b.begin(), b.end());
std::cout << "Yes" << "\n";
for (int i = 0; i < n; ++i) {
std::cout << c[i] << " \n"[i == n - 1];
}
} else {
std::cout << "No" << '\n';
}
} else if (k < 0) {
if (s >= k) {
c.insert(c.end(), a.begin(), a.end());
c.insert(c.end(), b.begin(), b.end());
std::cout << "Yes" << "\n";
for (int i = 0; i < n; ++i) {
std::cout << c[i] << " \n"[i == n - 1];
}
} else {
std::cout << "No" << "\n";
}
} else {
c.insert(c.end(), b.begin(), b.end());
c.insert(c.end(), a.begin(), a.end());
std::cout << "Yes" << "\n";
for (int i = 0; i < n; ++i) {
std::cout << c[i] << " \n"[i == n - 1];
}
}
}
D - Conquer a New Region
分析
由于要让所有点到这个点的边权最小值之和最大,考虑把边按从大到小插入。
此时每次并入的边权值都是当前已并入边中最小的,那么只要判断并入哪个集合更优。
最后用并查集维护即可。
代码实现
#include <bits/stdc++.h>
#ifdef LOCAL
#include "algo/debug.hpp"
#else
#define debug(...) 42
#endif
#define int long long
struct DSU {
std::vector<int> p, siz, d;
DSU(int n) { init(n); }
void init(int n) {
p.resize(n);
std::iota(p.begin(), p.end(), 0);
siz.assign(n, 1);
d.assign(n, 0);
}
int leader(int x) {
while (x != p[x]) x = p[x] = p[p[x]];
return x;
}
bool same(int x, int y) { return leader(x) == leader(y); }
bool merge(int x, int y, int z) {
x = leader(x), y = leader(y);
if (x == y) return false;
int dx = d[x] + siz[y] * z;
int dy = d[y] + siz[x] * z;
if (dx > dy) {
d[x] = dx;
siz[x] += siz[y], p[y] = x;
} else {
d[y] = dy;
siz[y] += siz[x], p[x] = y;
}
return true;
}
int size(int x) { return siz[leader(x)]; }
int dist(int x) { return d[leader(x)]; }
};
main() {
std::cin.tie(nullptr)->sync_with_stdio(false);
int n;
while (std::cin >> n) {
std::vector<std::array<int, 3>> edges(n - 1);
for (auto &[c, a, b] : edges) {
std::cin >> a >> b >> c;
a--, b--;
}
std::sort(edges.rbegin(), edges.rend());
DSU dsu(n);
for (auto [c, a, b] : edges) {
dsu.merge(a, b, c);
}
std::cout << dsu.dist(0) << '\n';
}
}