比赛链接
A. 智乃的天平
Solution
直接判断是否有 w ∈ { a , b , a + b , ∣ a − b ∣ } . w \in \lbrace a, b, a + b, |a - b| \rbrace. w∈{a,b,a+b,∣a−b∣}.
C++ Code
#include <bits/stdc++.h>using i64 = long long;
using u64 = unsigned long long;
using u32 = unsigned;
using i80 = __int128_t;
using u80 = unsigned __int128_t;
using f64 = double;
using f80 = long double;
using i16 = short;
using u16 = unsigned short;constexpr f64 pi = std::numbers::pi;int main() {std::ios::sync_with_stdio(false);std::cin.tie(nullptr);int a, b, w;std::cin >> a >> b >> w;std::array<int, 4> v{a, b, a + b, std::abs(a - b)};std::cout << (std::ranges::count(v, w) ? "Yes\n": "No\n");return 0;
}
B. 智乃爬山
Solution
直接按照题意模拟求最大值即可。
C++ Code
#include <bits/stdc++.h>using i64 = long long;
using u64 = unsigned long long;
using u32 = unsigned;
using i80 = __int128_t;
using u80 = unsigned __int128_t;
using f64 = double;
using f80 = long double;
using i16 = short;
using u16 = unsigned short;constexpr f64 pi = std::numbers::pi;template<class T>
std::istream &operator>>(std::istream &is, std::vector<T> &v) {for (auto &x: v) {is >> x;}return is;
}int main() {std::ios::sync_with_stdio(false);std::cin.tie(nullptr);int n;std::cin >> n;std::vector<int> a(n);std::cin >> a;int ans = -1;for (int i = 1; i + 1 < n; i++) {if (a[i - 1] < a[i] and a[i] > a[i + 1]) {ans = std::max(ans, a[i] - (a[i - 1] + a[i + 1]) / 2);}}std::cout << ans << "\n";return 0;
}
C. 智乃放球
题目大意
给定 m m m 个容量无限大的空桶( ∀ c i = 0 \forall c_i = 0 ∀ci=0)以及一个正整数 k k k。现在做 n n n 轮操作,每轮操作如下:
- 选定桶 i i i,它有 c i c_i ci 个小球,现在放入一个小球,令 c i : = ( c i + 1 ) ( m o d k ) c_i := (c_i + 1) \pmod k ci:=(ci+1)(modk);
问是否存在一种方案,使得 n n n 次操作结束后有 ∑ i = 1 m c i = q \sum\limits_{i = 1}^{m}c_i = q i=1∑mci=q
数据范围
- 1 ≤ T ≤ 1 0 5 , 1 \leq T \leq 10^5, 1≤T≤105,
- 1 ≤ n , m , k ≤ 1 0 9 , 1 \leq n, m, k \leq 10^9, 1≤n,m,k≤109,
- 1 ≤ q ≤ n . 1 \leq q \leq n. 1≤q≤n.
Solution
首先, ( n − q ) (n - q) (n−q) 一定要是 k k k 的倍数,因为我们最多只能 k k k 个 k k k 个干掉小球。
其次, q ≤ m ⋅ ( k − 1 ) q \leq m\cdot (k - 1) q≤m⋅(k−1),因为一旦数量达到 k k k 就会变为 0 0 0。
时间复杂度 O ( T ) O(T) O(T)
C++ Code
#include <bits/stdc++.h>using i64 = long long;
using u64 = unsigned long long;
using u32 = unsigned;
using i80 = __int128_t;
using u80 = unsigned __int128_t;
using f64 = double;
using f80 = long double;
using i16 = short;
using u16 = unsigned short;constexpr f64 pi = std::numbers::pi;std::mt19937 rng(std::chrono::steady_clock::now().time_since_epoch().count());void solve() {int n, m, k, q;std::cin >> n >> m >> k >> q;if ((n - q) % k == 0 and q <= static_cast<i64>(k - 1) * m) {std::cout << "Yes\n";} else {std::cout << "No\n";}
}int main() {std::ios::sync_with_stdio(false);std::cin.tie(nullptr);std::cout << std::fixed << std::setprecision(12);int T = 1;std::cin >> T;while (T--) {solve();}return 0;
}
D. 智乃的“K”叉树
题目大意
给定一棵 n n n 个结点的树,设以第 i ∈ [ 1 , n ] i \in [1, n] i∈[1,n] 个结点为根的树是 k i k_i ki 叉树,求 min i = 1 n k i \min\limits_{i = 1}^{n}k_i i=1minnki 以及对应的 i i i。
- 一棵树为 k k k 叉树,当且仅当存在一个结点 u u u 有 k k k 个儿子;
- 如果有多个结点满足要求,输出任意一个 i i i。
数据范围
- 2 ≤ n ≤ 1 0 5 . 2 \leq n \leq 10^5. 2≤n≤105.
Solution
首先,如果这棵树只有两个结点,那么 k = 1 k = 1 k=1,根随便找 1 1 1 或者 2 2 2 都行。
对于 n > 2 n > 2 n>2 的情况,记录每个结点的度,第 i i i 个结点的度位为 d e g [ i ] \rm{deg[i]} deg[i]。
设 m = max i = 1 n ( d e g [ i ] ) > 1 \rm{m = \max\limits_{i = 1}^{n}(deg[i])} > 1 m=i=1maxn(deg[i])>1,我们先把这个最大度对应的 i i i 提到根节点,此时 i i i 有 m m m 个儿子,而剩下的结点最多只有 m − 1 m - 1 m−1 个儿子,这是一棵 m m m 叉树。
- 如果存在一个非根节点 j j j 有 m m m 个儿子,那么加上 j j j 到 j j j 父亲的那条边, j j j 的度就是 m + 1 m + 1 m+1 了,矛盾。
现在找一个结点 j j j 提上来作为新的根结点,要求 d e g [ j ] < m \rm{deg[j]} < m deg[j]<m, 那么这棵新树的度就是 m − 1 m - 1 m−1,答案就是 m − 1 m - 1 m−1 和 j j j。
- 首先,这样的结点一定可以找到,比如叶节点;
- 其次,现在我把 j j j 提上来作为新的根结点,会变化的只有原来的根结点 i i i 到 j j j 这条简单路径上的结点,我们把这个点集记为 S S S。
- 对 j j j 来说,所有与其相连的都成为了它的儿子,因为 j j j 是新的根,这样儿子数量就是 d e g [ j ] < m \rm{deg[j] < m} deg[j]<m;
- 对于 i i i 来说,它在简单路径上的儿子变成了 i i i 的父亲,损失一个儿子,儿子数量变为 m − 1 m - 1 m−1;
- 对于 ∀ u ∈ ( S ∖ { j , i } ) \forall u \in (S \setminus \{ j, i \}) ∀u∈(S∖{j,i}),都是 u u u 在简单路径上的儿子变成 u u u 的父亲,损失一条儿子边,其他儿子仍然是 u u u 的儿子,而 u u u 在简单路径上的父亲成为 u u u 的儿子,加了一条儿子边,这样总的儿子数保持不变,且由之前的条件可知, u u u 的儿子数均 < m < m <m。
时间复杂度 O ( n ) O(n) O(n)
C++ Code
#include <bits/stdc++.h>int main() {std::ios::sync_with_stdio(false);std::cin.tie(nullptr);int n;std::cin >> n;std::vector<int> deg(n);for (int i = 1; i < n; i++) {int u, v;std::cin >> u >> v;u--, v--;deg[u]++;deg[v]++;}int m = std::ranges::max(deg);if (m == 1) {std::cout << 1 << " " << 1 << "\n";return 0;}int p = std::ranges::find_if(deg, [&](int d) {return d < m;}) - deg.begin();std::cout << m - 1 << " " << p + 1 << "\n";return 0;
}
E/F. 智乃的“凑数”题
题目大意
给定长度为 n n n 的正整数数组 k k k 、正整数 x x x 以及询问次数 m m m,每个询问给出一个 x x x,要求将 k k k 分成两个非空数组 a , b a, b a,b,设长度分别是 n a , n b n_a, n_b na,nb,使得 ∑ i = 1 n a ∑ j = 1 n b ( a i ⋅ b j ) = x , \sum\limits_{i = 1}^{n_a}\sum\limits_{j = 1}^{n_b}(a_i \cdot b_j) = x, i=1∑naj=1∑nb(ai⋅bj)=x, 问能否实现,如果可以,输出其中一种方案。
数据范围
- 这里直接上 h a r d \rm{hard} hard 版本的数据范围;
- 1 ≤ n ≤ 500 , 1 \leq n \leq 500, 1≤n≤500,
- 1 ≤ m ≤ 1000 , 1 \leq m \leq 1000, 1≤m≤1000,
- 1 ≤ k i ≤ 1 0 4 , 1 \leq k_i \leq 10^4, 1≤ki≤104,
- 1 ≤ x ≤ 1 0 4 . 1 \leq x \leq 10^4. 1≤x≤104.
Solution
首先题目要求的双层求和其实可以化简。
∑ i = 1 n a ∑ j = 1 n b a i ⋅ b j = ( ∑ i = 1 n a a i ) ( ∑ j = 1 n b b j ) = s a ⋅ s b = x . \sum\limits_{i = 1}^{n_a}\sum\limits_{j = 1}^{n_b}a_i \cdot b_j = \left(\sum\limits_{i = 1}^{n_a}a_i\right)\left( \sum\limits_{j = 1}^{n_b}b_j \right) = \rm{s_a} \cdot s_b = x. i=1∑naj=1∑nbai⋅bj=(i=1∑naai)(j=1∑nbbj)=sa⋅sb=x.
其中 s a , s b s_a, s_b sa,sb 分别表示 a a a 数组的和以及 b b b 数组的和。
s a ⋅ s b = x s_a \cdot s_b = x sa⋅sb=x,意味着 s a s_a sa 是 x x x 的因数,所以我们只要考虑枚举 x x x 的因数作为 a a a 数组的和。
设 d p [ s a ] [ s b ] \rm{dp}[s_a][s_b] dp[sa][sb] 表示能否给 a a a 数组和为 s a s_a sa 的若干个整数,给 b b b 数组和为 s b s_b sb 的若干个整数。
对于当前遍历到的 k i k_i ki 进行状态转移:
- 若 s a ≥ k i s_a \geq k_i sa≥ki,则 d p [ s a ] [ s b ] = d p [ s a ] [ s b ] ∣ ∣ d p [ s a − k i ] [ s b ] \rm{dp[s_a][s_b] = dp[s_a][s_b] \mid \mid dp[s_a - k_i][s_b]} dp[sa][sb]=dp[sa][sb]∣∣dp[sa−ki][sb];
- 若 s b ≥ k i s_b \geq k_i sb≥ki,则 d p [ s a ] [ s b ] = d p [ s a ] [ s b ] ∣ ∣ d p [ s a ] [ s b − k i ] \rm{dp[s_a][s_b] = dp[s_a][s_b] \mid \mid dp[s_a][s_b - k_i]} dp[sa][sb]=dp[sa][sb]∣∣dp[sa][sb−ki]。
为了最终输出具体的 a , b a, b a,b数组,我们还需要开一个 p r e [ s a ] [ s b ] \rm{pre[s_a][s_b]} pre[sa][sb] 数组记录当前状态是由哪个状态 ( s a ′ , s b ′ ) (s_a', s_b') (sa′,sb′) 转移而来的。
初值条件: d p [ 0 ] [ 0 ] = t r u e \rm{dp[0][0] = true} dp[0][0]=true。
当然,如果直接这样 d p \rm{dp} dp 只能通过 E E E,但在 F F F 题里, x x x 的范围直达 1 0 4 10^4 104,如果开一个 1 0 4 ⋅ 1 0 4 10^4 \cdot 10^4 104⋅104 的数组肯定爆空间了,而且大概率超时,因此要考虑优化。
经过观察,我们发现 s a ⋅ s b = x s_a \cdot s_b = x sa⋅sb=x 是在提示我们可以将第二维空间开小一点,例如:
- s a = 1 s_a = 1 sa=1 时, s b = x s_b = x sb=x;
- s a = 2 s_a = 2 sa=2 时, s b = x 2 s_b = \frac{x}{2} sb=2x;
- s a = 3 s_a = 3 sa=3 时, s b = x 3 s_b = \frac{x}{3} sb=3x;
- ⋯ \cdots ⋯;
根据级数的知识,当 n n n 较大时, ∑ i = 1 n i ≈ l n n \sum\limits_{i = 1}^{n}i \approx \rm{lnn} i=1∑ni≈lnn,于是我们可以让第二维 s b s_b sb 动态开,最终的所占总空间的大小大约是 O ( 1 0 4 log 1 0 4 ) O(10^4\log 10^4) O(104log104),时间上也够用。
时间复杂度 O ( n V log V + m x + m n ) = O ( n V log V ) O(nV\log V + m\sqrt{x} + mn) = O(nV\log V) O(nVlogV+mx+mn)=O(nVlogV)
- V = 1 0 4 V = 10^4 V=104;
- n V log V nV\log V nVlogV 表示对每个数都要做一次上述状态转移;
- m x m\sqrt{x} mx 表示每次询问都要求 x x x 的因数;
- m n mn mn 表示如果找到了满足条件的 s a , s b s_a, s_b sa,sb,需要根据 p r e \rm{pre} pre 看选了哪些数给 a a a,哪些给 b b b,花费 O ( n ) O(n) O(n)。
C++ Code
#include <bits/stdc++.h>using i64 = long long;
using u64 = unsigned long long;
using u32 = unsigned;
using i80 = __int128_t;
using u80 = unsigned __int128_t;
using f64 = double;
using f80 = long double;
using i16 = short;
using u16 = unsigned short;constexpr f64 pi = std::numbers::pi;template<class T>
std::istream &operator>>(std::istream &is, std::vector<T> &v) {for (auto &x: v) {is >> x;}return is;
}
template<class T>
std::ostream &operator<<(std::ostream &os, const std::vector<T> &v) {os << v[0];for (size_t i = 1; i < v.size(); i++) {os << " " << v[i];}return os;
}constexpr int U = 10000;int main() {std::ios::sync_with_stdio(false);std::cin.tie(nullptr);int n, m;std::cin >> n >> m;std::vector<int> k(n);std::cin >> k;std::array<std::vector<std::pair<int, int>>, U + 1> pre{};std::array<std::vector<bool>, U + 1> dp{};for (int r = 0; r <= U; r++) {dp[r].assign(U / std::max(r, 1) + 1, false);pre[r].assign(U / std::max(r, 1) + 1, {});}dp[0][0] = true;for (int v: k) {for (int r = U; r >= 0; r--) {for (int c = U / std::max(r, 1); c >= 0; c--) {if (not dp[r][c]) {continue;}for (auto [dr, dc]: {std::pair {v, 0}, {0, v}}) {if (r + dr <= U and c + dc <= U / std::max(r + dr, 1) and not dp[r + dr][c + dc]) {dp[r + dr][c + dc] = true;pre[r + dr][c + dc] = {r, c};}}}}}while (m--) {int x;std::cin >> x;auto work = [&](int x) {for (int c = 1; c * c <= x; c++) {if (x % c) {continue;}int r = x / c;if (not dp[r][c]) {continue;}std::cout << "Yes\n";std::vector<int> row;std::vector<int> col;for (int x = r, y = c; x > 0 or y > 0; ) {auto [nx, ny] = pre[x][y];if (x != nx) {row.push_back(x - nx);} else {col.push_back(y - ny);}x = nx;y = ny;}std::cout << row.size() << " " << col.size() << "\n";std::cout << row << "\n";std::cout << col << "\n";return true;}return false;};if (not work(x)) {std::cout << "No\n";}}return 0;
}