比赛链接
A. 期末复习
题目大意
给定 T T T 组数据,每组给定三个正整数 n , m , k n, m, k n,m,k。
对于一个 n × m n \times m n×m 的矩形,现在要求你做以下操作若干次,问能否使得 至少 60 % 60\% 60% 的方块被选中,如果不能输出 − 1 -1 −1;否则每次操作要花费 k k k 元,求 最小花费。
- 每次操作选择一些小方块组成的 L \rm\pmb{L} L 形方格,要求这个 L \rm\pmb{L} L 一定不能是一根棍,也不能是一个点。
数据范围
- 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.
Solution1
一个显然的想法是每次选一个最大的 L \rm\pmb{L} L,即选择边界的 L \rm\pmb{L} L,这就启发我们从边界的 n + ( m − 1 ) n + (m - 1) n+(m−1) 个方块组成的 L \rm\pmb{L} L 选起,再转个 18 0 ∘ 180^{\circ} 180∘ 选择另一半的 ( n − 1 ) + ( m − 2 ) (n - 1) + (m - 2) (n−1)+(m−2) 个方块 L \rm\pmb{L} L,一直选到不能选,因此在正常情况下我们最多可以选择 min ( n , m ) − 1 \min(n, m) - 1 min(n,m)−1 轮 L \rm\pmb{L} L。
设 s u m = ⌈ n × m × 6 10 ⌉ \rm{sum = \lceil \frac{n \times m \times 6}{10} \rceil} sum=⌈10n×m×6⌉,表示至少要选择的方块数。
首先 min ( n , m ) = 1 \min(n, m) = 1 min(n,m)=1 那么一定无解,因为题目不允许选择一个竖直或者横着的棍子。
其次是 min ( n , m ) = 2 \min(n, m) = 2 min(n,m)=2 的情况。这种情况下需要特判。
- 如果 n + ( m − 1 ) ≥ s u m n + (m - 1) \geq \rm{sum} n+(m−1)≥sum,说明选了第一个边界 L \rm\pmb{L} L 就可以走人了,花费 k k k 元;
- 否则 n + ( m − 1 ) < s u m n + (m - 1) < \rm{sum} n+(m−1)<sum,说明不能直接把第一个边界 L \rm\pmb{L} L 全部选掉,应该留一块给对称的另一半,这样可以直接选完所有方块(想象一下 L \rm\pmb{L} L 和 7 \rm\pmb{7} 7 插在一起),花费 2 k 2k 2k 元。
min ( n , m ) > 2 \min(n, m) > 2 min(n,m)>2 的情况就一定可以满足了,后面给出证明,下面先谈计算。
设我们选了 c c c 轮,那就是 ∑ i = 1 c ( ( n − i + 1 ) + ( m − i ) ) = ∑ i = 1 c ( ( n + m ) − ( 2 i − 1 ) ) = ∑ i = 1 c ( n + m ) − ∑ i = 1 c ( 2 i − 1 ) = ( n + m ) ⋅ c − c 2 = ( n + m − c ) ⋅ c . \begin{align*} \sum\limits_{i = 1}^{c}((n - i + 1) + (m - i)) &= \sum\limits_{i = 1}^c((n + m) - (2i - 1)) \\ &= \sum\limits_{i = 1}^c(n + m) - \sum\limits_{i = 1}^{c}(2i - 1) \\ &= (n + m) \cdot c - c^2 \\ &= (n + m - c) \cdot c. \end{align*} i=1∑c((n−i+1)+(m−i))=i=1∑c((n+m)−(2i−1))=i=1∑c(n+m)−i=1∑c(2i−1)=(n+m)⋅c−c2=(n+m−c)⋅c.
而我们就是要求最小的 c c c,使得 ( n + m − c ) ⋅ c ≥ s u m (n + m - c) \cdot c \geq sum (n+m−c)⋅c≥sum,这可以通过 二分 实现。最终答案就是 c k ck ck。
下面是 min ( n , m ) > 2 \min(n, m) > 2 min(n,m)>2 一定可以满足题目要求的证明。
我们最多可以进行 min ( n , m ) − 1 \min(n, m) - 1 min(n,m)−1 轮,根据上面推导的公式,一共可以选择 ( n + m − ( min ( n , m ) − 1 ) ) ⋅ ( min ( n , m ) − 1 ) (n + m - (\min(n, m) - 1)) \cdot (\min(n, m) - 1) (n+m−(min(n,m)−1))⋅(min(n,m)−1) 个方块,根据 n + m = max ( n , m ) + min ( n , m ) n + m = \max(n, m) + \min(n, m) n+m=max(n,m)+min(n,m),化简一下就是 ( max ( n , m ) + 1 ) ⋅ ( min ( n , m ) − 1 ) (\max(n, m) + 1) \cdot (\min(n, m) - 1) (max(n,m)+1)⋅(min(n,m)−1) 根据对称性,不妨设 n > m n > m n>m,那么就是 ( n + 1 ) ( m − 1 ) (n + 1)(m - 1) (n+1)(m−1),即选择了 n m − ( n − m ) − 1 nm - (n - m) - 1 nm−(n−m)−1 个方块,我们只需说明 n m − ( n m − ( n − m ) − 1 ) = ( n − m ) + 1 nm - (nm - (n - m) - 1) = (n - m) + 1 nm−(nm−(n−m)−1)=(n−m)+1 最多只占总和 n m nm nm 的 40 % 40\% 40% 即可。
只需证 ( n − m ) + 1 ≤ ⌊ 2 n m 5 ⌋ . (n - m) + 1 \leq \lfloor \frac{2nm}{5} \rfloor. (n−m)+1≤⌊52nm⌋.
考虑 ⌊ x ⌋ ≥ x − 1 \lfloor x \rfloor \geq x - 1 ⌊x⌋≥x−1,于是我们再换一个目标,想办法证明 ( n − m ) + 1 ≤ 2 n m 5 − 1 ≤ ⌊ 2 n m 5 ⌋ . (n - m) + 1 \leq \frac{2nm}{5} - 1 \leq \lfloor \frac{2nm}{5} \rfloor. (n−m)+1≤52nm−1≤⌊52nm⌋.
化简一下得到 2 n m − 5 ( n − m ) ≥ 10 , 2nm - 5(n - m) \geq 10, 2nm−5(n−m)≥10, 即 ( 2 m − 5 ) n + 5 m ≥ 10. (2m - 5)n + 5m \geq 10. (2m−5)n+5m≥10.
由于 min ( n , m ) > 2 \min(n, m) > 2 min(n,m)>2,那就有 ( 2 m − 5 ) n ≥ ( 2 ⋅ 3 − 5 ) ⋅ 3 = 3 , 5 m ≥ 5 ⋅ 3 = 15 , (2m - 5)n \geq (2 \cdot 3 - 5) \cdot 3 = 3, \\ 5m \geq 5 \cdot 3 = 15, (2m−5)n≥(2⋅3−5)⋅3=3,5m≥5⋅3=15,
综上得到 ( 2 m − 5 ) n + 5 m ≥ 3 + 15 = 18 ≥ 10. (2m - 5)n + 5m \geq 3 + 15 = 18 \geq 10. (2m−5)n+5m≥3+15=18≥10.
n ≤ m n \leq m n≤m 的情况同理可证。
时间复杂度 O ( ∑ log n m ) O\rm{(\sum\log nm)} O(∑lognm)
C++ Code for Solution1
#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;
std::cin >> n >> m >> k;
int maxc = std::min(n, m);
if (maxc == 1) {
std::cout << -1 << "\n";
return;
}
i64 sum = (3LL * n * m - 1) / 5 + 1;
if (maxc == 2) {
int ans = k * (1 + (sum > n + m - 1));
std::cout << ans << "\n";
return;
}
auto calc = [&](int c) {
return static_cast<i64>(n + m - c) * c;
};
int lo = 1, hi = maxc;
while (lo + 1 < hi) {
int m = lo + hi >> 1;
if (calc(m) >= sum) {
hi = m;
} else {
lo = m;
}
}
std::cout << static_cast<i64>(k) * hi << "\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;
}
Solution2
上面说到,求使得 ( n + m − c ) ⋅ c ≥ s u m (n + m - c) \cdot c \geq \rm{sum} (n+m−c)⋅c≥sum 的最小的 c c c 可以用二分,但是注意到这是个关于 c c c 的二次不等式,所以可以考虑用求根公式 O ( 1 ) O(1) O(1) 求解。
把不等式变为 c 2 − ( n + m ) c + s u m ≤ 0 c^2 - (n + m)c + \rm{sum} \leq 0 c2−(n+m)c+sum≤0 后,考虑 Δ = ( n + m ) 2 − 4 s u m ≥ ( 2 n m ) 2 − 4 s u m = 4 ( n m − s u m ) > 0 , \Delta = (n + m)^2 - 4 \ \rm{sum} \geq (2 \sqrt{nm})^2 - 4 \ \rm{sum} = 4(nm - \rm{sum}) > 0, Δ=(n+m)2−4 sum≥(2nm)2−4 sum=4(nm−sum)>0, 再用求根公式得到两个根
c 1 = ( n + m ) − ( n + m ) 2 − 4 s u m 2 , c 2 = ( n + m ) + ( n + m ) 2 − 4 s u m 2 . c_1 = \frac{(n + m) - \sqrt{(n + m)^2 - 4 \ \rm{sum}}}{2}, \\ c_2 = \frac{(n + m) + \sqrt{(n + m)^2 - 4 \ \rm{sum}}}{2}. c1=2(n+m)−(n+m)2−4 sum,c2=2(n+m)+(n+m)2−4 sum.
那么 c c c 的范围就是 [ max ( ⌈ c 1 ⌉ , 1 ) , min ( ⌊ c 2 ⌋ , min ( n , m ) − 1 ) ] [\max(\lceil c_1 \rceil, 1), \ \min(\lfloor c_2 \rfloor, \min(n, m) - 1)] [max(⌈c1⌉,1), min(⌊c2⌋,min(n,m)−1)]。
所以满足条件的最小的 c c c 是 max ( ⌈ c 1 ⌉ , 1 ) \max(\lceil c_1 \rceil, 1) max(⌈c1⌉,1),而 ( n + m ) > ( n + m ) 2 − 4 s u m (n + m) > \sqrt{(n + m)^2 - 4 \ \rm{sum}} (n+m)>(n+m)2−4 sum,因此 ⌈ c 1 ⌉ ≥ 1 \lceil c_1 \rceil \geq 1 ⌈c1⌉≥1,因此最小的 c c c 就是 ⌈ c 1 ⌉ \lceil c_1 \rceil ⌈c1⌉。
至于解的存在性在 S o l u t i o n 1 \rm\pmb{Solution1} Solution1 中已有证明。
时间复杂度 O ( T ) O(T) O(T)
C++ Code for Solution2
#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;
std::cin >> n >> m >> k;
int maxc = std::min(n, m);
if (maxc == 1) {
std::cout << -1 << "\n";
return;
}
i64 sum = (3LL * n * m - 1) / 5 + 1;
if (maxc == 2) {
std::cout << k * (1 + (sum > n + m - 1)) << "\n";
return;
}
i64 c = std::ceil(((n + m) - std::sqrt(static_cast<i64>(n + m) * (n + m) - 4 * sum)) / 2);
std::cout << c * k << "\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;
}
B. 洛杉矶的火
题面大意
给定 n n n 个点 ( x i , y i ) , i ∈ [ 1 , n ] (x_i, y_i) , i \in [1, n] (xi,yi),i∈[1,n] 以及一个纵坐标 h h h,你现在在 ( x 0 , y 0 ) (x_0, y_0) (x0,y0),要求你以某个顺序走完这 n n n 个点,求走过的最短距离(用曼哈顿距离)。
- 注意题目 特别规定,每次从 ( x i k , y i k ) (x_{i_k}, y_{i_k}) (xik,yik) 出发,必须先以某种方式(横着+竖着)走到纵坐标 h h h,即 ( x ′ , h ) (x', h) (x′,h),再走到 ( x i k + 1 , y i k + 1 ) (x_{i_{k + 1}}, y_{i_{k + 1}}) (xik+1,yik+1);
- 其中 i k i_k ik 表示第 k k k 个经过的点的原下标为 i k i_k ik, x ′ x' x′ 表示任意横坐标。
数据范围
- 1 ≤ ∑ n ≤ 2 ⋅ 1 0 5 , 1 \leq \sum n \leq 2 \cdot 10^5, 1≤∑n≤2⋅105,
- − 1 0 9 ≤ h , x 0 , y 0 ≤ 1 0 9 , -10^9 \leq h, x_0, y_0 \leq 10^9, −109≤h,x0,y0≤109,
- − 1 0 9 ≤ x i , y i ≤ 1 0 9 , -10^9 \leq x_i, y_i \leq 10^9, −109≤xi,yi≤109,
- 保证任意两对 ( x i , y i ) (x_i, y_i) (xi,yi) 和 ( x j , y j ) (x_j, y_j) (xj,yj) 不同。
Solution
首先假设走的顺序 i 1 , i 2 , ⋯ , i n i_1, i_2, \cdots, i_n i1,i2,⋯,in,然后列公式。特别规定 i 0 = 0 i_0 = 0 i0=0。
每一次行走流程按顺序展示如下:
- 从 ( x i k − 1 , y i k − 1 ) (x_{i_{k - 1}}, y_{i_{k - 1}}) (xik−1,yik−1) 到某个 ( x ′ , h ) (x', h) (x′,h),显然 x ′ = x i k − 1 x' = x_{i_{k - 1}} x′=xik−1 最佳,那么这一段行走的曼哈顿距离就是 ∣ y i k − 1 − h ∣ , |y_{i_{k - 1}} - h|, ∣yik−1−h∣,
- 从 ( x i k − 1 , h ) (x_{i_{k - 1}}, h) (xik−1,h) 到 ( x i k , y i k ) (x_{i_k}, y_{i_k}) (xik,yik),这一段行走的曼哈顿距离为 ∣ x i k − x i k − 1 ∣ + ∣ y i k − h ∣ . |x_{i_k} - x_{i_{k - 1}}| + |y_{i_k} - h|. ∣xik−xik−1∣+∣yik−h∣.
所以总共走过的距离是 ∑ k = 1 n ∣ y i k − 1 − h ∣ + ∣ x i k − x i k − 1 ∣ + ∣ y i k − h ∣ . \sum\limits_{k = 1}^{n}|y_{i_{k - 1}} - h| + |x_{i_k} - x_{i_{k - 1}}| + |y_{i_k} - h|. k=1∑n∣yik−1−h∣+∣xik−xik−1∣+∣yik−h∣.
整理一下就会变成 ( ∣ y 0 − h ∣ + 2 ( ∑ k = 1 n ∣ y i k − h ∣ ) ) + ( ( ∑ k = 1 n ∣ x i k − x i k − 1 ∣ ) − ∣ y i n − h ∣ ) . \left(|y_{0} - h| + 2\left(\sum\limits_{k = 1}^{n}|y_{i_k} - h|\right)\right) + \left(\left( \sum\limits_{k = 1}^{n}|x_{i_k} - x_{i_{k - 1}}| \right) - |y_{i_n} - h|\right). (∣y0−h∣+2(k=1∑n∣yik−h∣))+((k=1∑n∣xik−xik−1∣)−∣yin−h∣).
可以发现,前半括号里都是不变量,而后半括号主要跟结尾的 y i n y_{i_n} yin 以及遍历 x x x 的顺序有关,这就启发我们枚举结尾处坐标 ( x i n , y i n ) (x_{i_n}, y_{i_n}) (xin,yin),然后用某种方式知道遍历的顺序。
那么怎么知道遍历的顺序呢?容易想到,如果能尽量让 x x x 升序或者降序是最好的,实在不行,就要先遍历 终点关于起点异侧的那些点。
比如说终点的横坐标 x e = 0 x_e = 0 xe=0,起点的横坐标 x s = 2 x_s = 2 xs=2,那我们就要优先选择遍历 x ′ > 2 x' > 2 x′>2 的部分。设这 n + 1 n + 1 n+1 个点中最小的横坐标为 m i n x \rm{minx} minx,最大的横坐标为 m a x x \rm{maxx} maxx,这样贪心得到的结果就是 2 ( m a x x − m i n x ) − ∣ x e − x s ∣ . \rm{2 \ (maxx - minx) - |x_e - x_s|}. 2 (maxx−minx)−∣xe−xs∣.
所以只要先预处理好前面的不变量,以及 2 ( m a x x − m i n x ) \rm{2 \ (maxx - minx)} 2 (maxx−minx),就可以 O ( n ) O(n) O(n) 出结果了。
时间复杂度 O ( ∑ n ) O(\sum 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;
std::mt19937 rng(std::chrono::steady_clock::now().time_since_epoch().count());
void solve() {
int n, h, x0, y0;
std::cin >> n >> h >> x0 >> y0;
std::vector<int> x(n);
std::vector<int> y(n);
for (int i = 0; i < n; i++) {
std::cin >> x[i] >> y[i];
}
i64 sum = 0;
for (int i = 0; i < n; i++) {
sum += std::abs(y[i] - h);
}
int maxx = std::ranges::max(x);
int minx = std::ranges::min(x);
i64 dis = 2LL * (std::max(x0, maxx) - std::min(x0, minx));
i64 ans = std::numeric_limits<i64>::max() / 4;
for (int i = 0; i < n; i++) {
i64 res = 2 * sum - std::abs(y[i] - h);
res += dis - std::abs(x[i] - x0);
ans = std::min(ans, res);
}
std::cout << ans + std::abs(y0 - h) << "\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;
}
C. 好数
题目大意
我们称正整数 x x x 为好数当且仅当存在正整数 l , r l, r l,r 满足 l < r l < r l<r,使得 x = ∑ i = l r i x = \sum\limits_{i = l}^{r}i x=i=l∑ri。
给定 T T T 组数据,每组给出一个区间 [ L , R ] [L, R] [L,R]。
对于每个 [ L , R ] [L, R] [L,R],问 [ L , R ] [L, R] [L,R] 中有多少个好数。
数据范围
- 1 ≤ T ≤ 1 0 4 , 1 \leq T \leq 10^4, 1≤T≤104,
- 1 ≤ L ≤ R ≤ 1 0 18 . 1 \leq L \leq R \leq 10^{18}. 1≤L≤R≤1018.
Solution
这个题其实是个经典结论了。我们考虑 x = ∑ i = l r i = ( r + l ) ( r − l + 1 ) 2 , x = \sum\limits_{i = l}^{r}i = \frac{(r + l)(r - l + 1)}{2}, x=i=l∑ri=2(r+l)(r−l+1), 不论 l , r l, r l,r 的奇偶, r + l r + l r+l 和 r − l r - l r−l 的奇偶性一定是一样的,所以 ( r + l ) ( r − l + 1 ) (r + l)(r - l + 1) (r+l)(r−l+1) 一定是一个偶(奇)数一个奇(偶)数,且那个奇数一定 ≥ 3 \geq 3 ≥3(题目规定 l < r l < r l<r),而最后还要除以 2 2 2,所以 x x x 一定不可能是 2 k ( k ∈ N ) 2^k(k \in N) 2k(k∈N)。
那么除了 2 k 2^k 2k,其他的正整数都能凑出来吗?打表可知确实可以,下面进行证明。
- 若 x x x 是( ≥ 3 \geq 3 ≥3 的)奇数,那么有 x = x − 1 2 + x + 1 2 , x = \frac{x - 1}{2} + \frac{x + 1}{2}, x=2x−1+2x+1, 取 l = x − 1 2 , r = x + 1 2 l = \frac{x - 1}{2}, r = \frac{x + 1}{2} l=2x−1,r=2x+1 即可。
- 若
x
x
x 是(
≠
2
k
,
∀
k
∈
N
\neq 2^k, \ \forall k \in N
=2k, ∀k∈N) 的偶数,那么一定有
x
=
a
⋅
b
,
x = a \cdot b,
x=a⋅b, 不妨设
a
a
a 为奇数(一定
≥
3
\geq 3
≥3),
b
b
b 为偶数。
- 若 b > a − 1 2 b > \frac{a - 1}{2} b>2a−1,那就构造 ∑ i = b − a − 1 2 b + a − 1 2 i = a − 1 2 ( 2 b ) + b = a ⋅ b = x , \sum\limits_{i = b - \frac{a - 1}{2}}^{b + \frac{a - 1}{2}}i = \frac{a - 1}{2} (2b)+ b = a \cdot b = x, i=b−2a−1∑b+2a−1i=2a−1(2b)+b=a⋅b=x,
- 否则就有 b ≤ a − 1 2 < a + 1 2 b \leq \frac{a - 1}{2} < \frac{a + 1}{2} b≤2a−1<2a+1,于是构造 ∑ i = a + 1 2 − b a + 1 2 + b − 1 i = ( ∑ i = a + 1 2 − b a + 1 2 + b i ) − ( a + 1 2 + b ) = b ⋅ ( a + 1 ) + a + 1 2 − ( a + 1 2 + b ) = b ⋅ ( a + 1 ) − b = b ⋅ a = x \begin{align*}\sum\limits_{i = \frac{a + 1}{2} - b}^{\frac{a + 1}{2} + b - 1}i &= \left(\sum\limits_{i = \frac{a + 1}{2} - b}^{\frac{a + 1}{2} + b}{i} \right) - \left(\frac{a + 1}{2} + b\right) \\ &= b \cdot (a + 1) + \frac{a + 1}{2} - \left(\frac{a + 1}{2} + b\right) \\ &= b \cdot (a + 1) - b \\ &= b \cdot a \\ &= x \end{align*} i=2a+1−b∑2a+1+b−1i= i=2a+1−b∑2a+1+bi −(2a+1+b)=b⋅(a+1)+2a+1−(2a+1+b)=b⋅(a+1)−b=b⋅a=x
综上,除了 2 k ( k ∈ N ) 2^k(k \in N) 2k(k∈N) 都能用连续自然数的和凑出来。
时间复杂度 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());
int lg(i64 x) {
return x > 0 ? std::__lg(x) + 1: 0;
}
void solve() {
i64 L, R;
std::cin >> L >> R;
L--;
std::cout << (R - L) - (lg(R) - lg(L)) << "\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;
}
E. 交换offer
题目大意
给定 T T T 组数据,每组给定一个正整数 n n n,表示人数。
这 n n n 个人每人有一份 o f f e r \rm{offer} offer,每个人都可以将自己的 o f f e r \rm{offer} offer 随机交给其他人,问所有人都拿不到自己原来 o f f e r \rm{offer} offer 的概率是多少,答案对 998244353 998244353 998244353 取模。
数据范围
- 1 ≤ T ≤ 1 0 4 , 1 \leq T \leq 10^4, 1≤T≤104,
- 2 ≤ n ≤ 2 ⋅ 1 0 6 . 2 \leq n \leq 2 \cdot 10^6. 2≤n≤2⋅106.
Solution
其实就是一个全错排问题,设 D n D_n Dn 表示 n n n 个人的全错排数量,那么递推式就是
D n = ( n − 1 ) ( D n − 1 + D n − 2 ) , D_n = (n - 1)(D_{n - 1} + D_{n - 2}), Dn=(n−1)(Dn−1+Dn−2),
初值条件: D 1 = 0 , D 2 = 1 D_1 = 0, D_2 = 1 D1=0,D2=1。
其含义可以用 d p \rm{dp} dp 的转移方程来理解。
假设我们要将元素 n n n 排列到某个位置,但由于是错排,它不能出现在位置 n n n 上,所以我们有 n − 1 n - 1 n−1 种选择来将元素 n n n 放到其他位置。
假设 元素 n n n 被放置在位置 i ∈ [ 1 , n ) i \in [1, n) i∈[1,n),那么我们就需要考虑剩余 n − 1 n - 1 n−1 个元素的错排。这个时候,我们有两种情况需要处理:
- 元素 i i i 被放置到位置 n n n。这种情况下,剩下的 n − 2 n - 2 n−2 个元素错排;
- 元素 i i i 被放置到其他位置( ≠ n \neq n =n),此时我们需要将剩余的 n − 1 n - 1 n−1 个元素错排。
最终答案就是 D n ( n − 1 ) n . \frac{D_n}{(n - 1)^n}. (n−1)nDn.
时间复杂度 O ( max ( n ) + ∑ log n ) O(\max(\rm{n}) + \sum\log n) O(max(n)+∑logn)
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());
template<class T>
constexpr T power(T a, i64 b) {
T res = 1;
for (; b; b /= 2, a *= a) {
if (b % 2) {
res *= a;
}
}
return res;
}
template<int P>
struct MInt {
int x;
constexpr MInt() : x{} {}
constexpr MInt(i64 x) : x{norm(x % getMod())} {}
static int Mod;
constexpr static int getMod() {
if (P > 0) {
return P;
} else {
return Mod;
}
}
constexpr static void setMod(int Mod_) {
Mod = Mod_;
}
constexpr int norm(int x) const {
if (x < 0) {
x += getMod();
}
if (x >= getMod()) {
x -= getMod();
}
return x;
}
constexpr int val() const {
return x;
}
explicit constexpr operator int() const {
return x;
}
constexpr MInt operator-() const {
MInt res;
res.x = norm(getMod() - x);
return res;
}
constexpr MInt inv() const {
assert(x != 0);
return power(*this, getMod() - 2);
}
constexpr MInt &operator*=(MInt rhs) & {
x = 1LL * x * rhs.x % getMod();
return *this;
}
constexpr MInt &operator+=(MInt rhs) & {
x = norm(x + rhs.x);
return *this;
}
constexpr MInt &operator-=(MInt rhs) & {
x = norm(x - rhs.x);
return *this;
}
constexpr MInt &operator/=(MInt rhs) & {
return *this *= rhs.inv();
}
friend constexpr MInt operator*(MInt lhs, MInt rhs) {
MInt res = lhs;
res *= rhs;
return res;
}
friend constexpr MInt operator+(MInt lhs, MInt rhs) {
MInt res = lhs;
res += rhs;
return res;
}
friend constexpr MInt operator-(MInt lhs, MInt rhs) {
MInt res = lhs;
res -= rhs;
return res;
}
friend constexpr MInt operator/(MInt lhs, MInt rhs) {
MInt res = lhs;
res /= rhs;
return res;
}
friend constexpr std::istream &operator>>(std::istream &is, MInt &a) {
i64 v;
is >> v;
a = MInt(v);
return is;
}
friend constexpr std::ostream &operator<<(std::ostream &os, const MInt &a) {
return os << a.val();
}
friend constexpr bool operator==(MInt lhs, MInt rhs) {
return lhs.val() == rhs.val();
}
friend constexpr bool operator!=(MInt lhs, MInt rhs) {
return lhs.val() != rhs.val();
}
};
template<>
int MInt<0>::Mod = 998244353;
template<int V, int P>
constexpr MInt<P> CInv = MInt<P>(V).inv();
constexpr int P = 998244353;
using Z = MInt<P>;
std::vector<Z> D;
void init(int n) {
D.assign(n + 1, 0);
D[2] = 1;
for (int i = 3; i <= n; i++) {
D[i] = (i - 1) * (D[i - 1] + D[i - 2]);
}
}
void solve() {
int n;
std::cin >> n;
std::cout << (D[n] / power(Z(n - 1), n)) << "\n";
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout << std::fixed << std::setprecision(12);
init(2000000);
int T = 1;
std::cin >> T;
while (T--) {
solve();
}
return 0;
}
F. 植树节
题目大意
原题是 c o d e f o r c e s \rm{codeforces} codeforces 的一道题:CF1363C Game On Leaves。
给定一棵 N N N 个结点的树, A l i c e \rm{Alice} Alice 和 B o b \rm{Bob} Bob 两人在树上博弈。一开始你指定了一个结点 X ∈ [ 1 , N ] X \in [1, N] X∈[1,N]。现在他们要轮流删除结点, A l i c e \rm{Alice} Alice 先手。
- 每次删除的结点只能是度为 1 1 1 的结点(也就是叶节点),且同时要删掉这条悬挂边。
谁先删掉 X X X 谁就获胜,问谁能获胜。
数据范围
- 1 ≤ X ≤ N ≤ 5 ⋅ 1 0 5 . 1 \leq X \leq N \leq 5 \cdot 10^5. 1≤X≤N≤5⋅105.
Solution
如果 X X X 是叶结点,那么先手必胜。
如果 X X X 不是叶节点,我们可以把 X X X 拎起来,让 X X X 作为根节点,然后按照层序遍历的逆序往上删除,那么删到最后一定剩下两个结点,一个是 X X X,一个是下面挂着的它的其中一个儿子 Y Y Y,此时 X X X 也是叶结点了,所以先到这个状态的必胜。
如果 ( N − 1 ) ≡ 1 ( m o d 2 ) (N - 1) \equiv 1 \pmod{2} (N−1)≡1(mod2),说明上面所说的状态是奇数次被访问到的,也就是 A l i c e \rm{Alice} Alice 必胜,因此 N ≡ 0 ( m o d 2 ) N \equiv 0 \pmod{2} N≡0(mod2) 则先手必胜。
时间复杂度 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;
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 X;
std::cin >> X;
X--;
if (deg[X] == 1 or N % 2 == 0) {
std::cout << "xiaonian wins!\n";
} else {
std::cout << "coldtree wins!\n";
}
return 0;
}
G. 逻辑门
题目大意
这个只能在题面里看图,没法细说。
Solution
就是跟找规律一样,最终出来的是个这样的结构,大家看久一点就能发现规律了,太难用语言描述了(其中 ⊕ \oplus ⊕ 表示异或, ⊗ \otimes ⊗ 表示按位与, ∣ \mid ∣ 表示按位或)。
( a 1 ⊕ a 2 ) ( a 1 ⊗ a 2 ) ⊕ ( a 3 ⊕ a 4 ) ( ( ( a 1 ⊗ a 2 ) ⊗ ( a 3 ⊕ a 4 ) ) ∣ ( a 3 ⊗ a 4 ) ) ⊕ ( a 5 ⊕ a 6 ) ( ( ( ( ( a 1 ⊗ a 2 ) ⊗ ( a 3 ⊕ a 4 ) ) ∣ ( a 3 ⊗ a 4 ) ) ⊗ ( a 5 ⊕ a 6 ) ) ∣ ( a 5 ⊗ a 6 ) ) ⊕ ( a 7 ⊕ a 8 ) ⋯ \begin{align*} (&a_1 \oplus a_2) \\ (&a_1 \otimes a_2) \oplus (a_3 \oplus a_4) \\ (((&a_1 \otimes a_2) \otimes (a_3 \oplus a_4)) \mid (a_3 \otimes a_4)) \oplus (a_5 \oplus a_6) \\ (((((&a_1 \otimes a_2) \otimes (a_3 \oplus a_4)) \mid (a_3 \otimes a_4)) \otimes (a_5 \oplus a_6)) \mid (a_5 \otimes a_6)) \oplus (a_7 \oplus a_8) \\ &\cdots \end{align*} ((((((((((a1⊕a2)a1⊗a2)⊕(a3⊕a4)a1⊗a2)⊗(a3⊕a4))∣(a3⊗a4))⊕(a5⊕a6)a1⊗a2)⊗(a3⊕a4))∣(a3⊗a4))⊗(a5⊕a6))∣(a5⊗a6))⊕(a7⊕a8)⋯
时间复杂度 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::ostream &operator<<(std::ostream &os, const std::vector<T> &v) {
for (const auto &x: v) {
os << x;
}
return os;
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n;
std::cin >> n;
std::vector<int> a(n);
for (int i = 0; i < n; i++) {
char c;
std::cin >> c;
a[i] = c - '0';
}
std::vector<int> ans(n / 2 + 1);
int sum = a[0] & a[1];
ans[n / 2] = a[0] ^ a[1];
ans[n / 2 - 1] = sum ^ (a[2] ^ a[3]);
for (int i = 4; i + 2 <= n; i += 2) {
sum = (sum & (a[i - 2] ^ a[i - 1])) | (a[i - 2] & a[i - 1]);
ans[n / 2 - i / 2] = sum ^ (a[i] ^ a[i + 1]);
}
sum = (sum & (a[n - 2] ^ a[n - 1])) | (a[n - 2] & a[n - 1]);
ans[0] = sum;
std::cout << ans << "\n";
return 0;
}
I. 找出躺赢狗
Solution
模拟题,直接存分数和名字排序即可,这里浮点误差远到不了 0.1 0.1 0.1 的程度,但是我为了写 i n t \rm{int} int 的(比较好看),所以用字符串处理了一下。
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 n;
std::cin >> n;
std::vector<std::pair<int, std::string>> ans;
for (int i = 0; i < n; i++) {
std::string s;
std::string t;
std::cin >> s >> t;
int score = 0;
int p = t.find('.');
if (p == -1) {
score = std::stoi(t) * 10;
} else {
score = std::stoi(t.substr(0, p)) * 10 + std::stoi(t.substr(p + 1));
}
if (score <= 30) {
ans.emplace_back(score, s);
}
}
std::ranges::sort(ans);
std::cout << ans.size() << "\n";
for (const auto &[score, name]: ans) {
std::cout << name << "\n";
}
return 0;
}
J. 音名
Solution
也是模拟题,直接开一个字符串 C D E F G A B \rm{CDEFGAB} CDEFGAB,然后找给定音名在这个字符串中的位置即可。
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());
using namespace std::literals::string_literals;
auto s = "CDEFGAB"s;
void solve() {
char a, b;
std::cin >> a >> b;
if (s.find(a) < s.find(b)) {
std::cout << b << "\n";
} else {
std::cout << a << "\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;
}
K. 昆明的雨
题目大意
给定一棵 n n n 个节点的树、一个正整数 m m m 和一个正整数数组 w w w。
- 长度为 n n n 的数组 w w w, w i w_i wi 表示第 i i i 个结点的权值;
- 若选择的结点满足 ( ∑ i i s c h o s e n w i ) ≤ m \left(\sum\limits_{i \ \ is \ \ chosen}w_i \right) \leq m (i is chosen∑wi)≤m,我们称其为 合法 的选择。
现在给定 q q q 个询问(每个询问独立),每个询问给出 x , y ∈ [ 1 , n ] x, y \in [1, n] x,y∈[1,n],若你只能选择从 1 1 1 到 x x x 的简单路径上的结点,且不能选择 y y y,求有多少种 合法 的选择方案,答案对 998244353 998244353 998244353 取模。
数据范围
- 2 ≤ n , m , q ≤ 5000 , 2 \leq n, m, q \leq 5000, 2≤n,m,q≤5000,
- 1 ≤ w i ≤ m , 1 \leq w_i \leq m, 1≤wi≤m,
- 1 ≤ x , y ≤ n , 1 \leq x, y \leq n, 1≤x,y≤n,
- 不保证 y y y 在 1 1 1 到 x x x 的简单路径上。
Solution
首先,如果没有不能选择 y y y 的限制,这就是一个基本的树上背包。
下面的讨论都是基于这棵树以 1 1 1 为根。
设 d p [ x ] [ s ] \rm{dp[x][s]} dp[x][s] 表示从 1 1 1 到 x x x 这条简单路径上选择的结点权值总和为 s s s 的方案数, y y y 为 x x x 其中一个儿子,显然有转移方程:
- 不选 y y y: d p [ y ] [ s ] : = d p [ y ] [ s ] + d p [ x ] [ s ] , ∀ s ∈ [ 0 , m ] , \rm{dp[y][s] := dp[y][s] + dp[x][s]}, \ \forall s \in [0, m], dp[y][s]:=dp[y][s]+dp[x][s], ∀s∈[0,m],
- 选择 y y y: d p [ y ] [ s ] : = d p [ y ] [ s ] + d p [ x ] [ s − w y ] , ∀ s ∈ [ w y , m ] . \rm{dp[y][s] := dp[y][s] + dp[x][s - w_y]}, \ \forall s \in [w_y, m]. dp[y][s]:=dp[y][s]+dp[x][s−wy], ∀s∈[wy,m].
初值条件: d p [ 1 ] [ w 1 ] = d p [ 1 ] [ 0 ] = 1 \rm{dp[1][w_1] = dp[1][0] = 1} dp[1][w1]=dp[1][0]=1。
- 注意只需要赋根节点的初值,其他结点的 d p \rm{dp} dp 值在转移过程中就会正确计算。
现在再考虑 y y y 可能是 x x x 的祖先的情况,这可以用 d f s \rm{dfs} dfs 序进行判断(见代码中的 i n , o u t \rm{in, out} in,out 数组)。
- 若 y y y 不是 x x x 的祖先,也就不在 1 1 1 到 x x x 的简单路径上,直接输出 ∑ s = 0 m d p [ x ] [ s ] . \sum\limits_{s = 0}^{m}\rm{dp[x][s]}. s=0∑mdp[x][s].
- 否则考虑另一个 d p \rm{dp} dp。设 n d p [ x ] [ s ] \rm{ndp[x][s]} ndp[x][s] 表示从 1 1 1 到 x x x 这条简单路径上选择的结点权值总和为 s s s 的方案数,且不选 x x x。那么就有 d p [ x ] [ s ] = n d p [ x ] [ s ] + n d p [ x ] [ s − w x ] , \rm{dp[x][s] = ndp[x][s] + ndp[x][s - w_x]}, dp[x][s]=ndp[x][s]+ndp[x][s−wx], 其中 n d p [ x ] [ s ] \rm{ndp}[x][s] ndp[x][s] 表示确定不选 x x x, n d p [ x ] [ s − w x ] \rm{ndp[x][s - w_x]} ndp[x][s−wx] 表示已经 选完 x x x 后,不再给选 x x x 的机会。那么移项后就是 n d p [ x ] [ s ] = d p [ x ] [ s ] − n d p [ x ] [ s − w x ] , \rm{ndp[x][s] = dp[x][s] - ndp[x][s - w_x]}, ndp[x][s]=dp[x][s]−ndp[x][s−wx], 同时我们发现 n d p \rm{ndp} ndp 第一维的 x x x 没用,所以直接去掉,变成 n d p [ s ] = d p [ x ] [ s ] − n d p [ s − w x ] . \rm{ndp[s] = dp[x][s] - ndp[s - w_x]}. ndp[s]=dp[x][s]−ndp[s−wx]. 最后输出 ∑ s = 0 m n d p [ s ] . \sum\limits_{s = 0}^{m}\rm{ndp[s]}. s=0∑mndp[s].
时间复杂度 O ( n m + q m ) O(\rm{nm + qm}) O(nm+qm)
- 预处理 d p \rm{dp} dp 是 O ( n m ) O(nm) O(nm);
- 最坏情况下每次询问都处理一遍 n d p \rm{ndp} ndp,所以是 O ( q m ) O(qm) O(qm)。
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>
constexpr T power(T a, i64 b) {
T res = 1;
for (; b; b /= 2, a *= a) {
if (b % 2) {
res *= a;
}
}
return res;
}
template<int P>
struct MInt {
int x;
constexpr MInt() : x{} {}
constexpr MInt(i64 x) : x{norm(x % getMod())} {}
static int Mod;
constexpr static int getMod() {
if (P > 0) {
return P;
} else {
return Mod;
}
}
constexpr static void setMod(int Mod_) {
Mod = Mod_;
}
constexpr int norm(int x) const {
if (x < 0) {
x += getMod();
}
if (x >= getMod()) {
x -= getMod();
}
return x;
}
constexpr int val() const {
return x;
}
explicit constexpr operator int() const {
return x;
}
constexpr MInt operator-() const {
MInt res;
res.x = norm(getMod() - x);
return res;
}
constexpr MInt inv() const {
assert(x != 0);
return power(*this, getMod() - 2);
}
constexpr MInt &operator*=(MInt rhs) & {
x = 1LL * x * rhs.x % getMod();
return *this;
}
constexpr MInt &operator+=(MInt rhs) & {
x = norm(x + rhs.x);
return *this;
}
constexpr MInt &operator-=(MInt rhs) & {
x = norm(x - rhs.x);
return *this;
}
constexpr MInt &operator/=(MInt rhs) & {
return *this *= rhs.inv();
}
friend constexpr MInt operator*(MInt lhs, MInt rhs) {
MInt res = lhs;
res *= rhs;
return res;
}
friend constexpr MInt operator+(MInt lhs, MInt rhs) {
MInt res = lhs;
res += rhs;
return res;
}
friend constexpr MInt operator-(MInt lhs, MInt rhs) {
MInt res = lhs;
res -= rhs;
return res;
}
friend constexpr MInt operator/(MInt lhs, MInt rhs) {
MInt res = lhs;
res /= rhs;
return res;
}
friend constexpr std::istream &operator>>(std::istream &is, MInt &a) {
i64 v;
is >> v;
a = MInt(v);
return is;
}
friend constexpr std::ostream &operator<<(std::ostream &os, const MInt &a) {
return os << a.val();
}
friend constexpr bool operator==(MInt lhs, MInt rhs) {
return lhs.val() == rhs.val();
}
friend constexpr bool operator!=(MInt lhs, MInt rhs) {
return lhs.val() != rhs.val();
}
};
template<>
int MInt<0>::Mod = 998244353;
template<int V, int P>
constexpr MInt<P> CInv = MInt<P>(V).inv();
constexpr int P = 998244353;
using Z = MInt<P>;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n, m;
std::cin >> n >> m;
std::vector<int> w(n);
std::cin >> w;
std::vector<std::vector<int>> adj(n);
for (int i = 1; i < n; i++) {
int u, v;
std::cin >> u >> v;
u--, v--;
adj[u].push_back(v);
adj[v].push_back(u);
}
std::vector dp(n, std::vector<Z>(m + 1));
dp[0][w[0]] = 1;
dp[0][0] = 1;
int idx = 0;
std::vector<int> in(n);
std::vector<int> out(n);
auto dfs = [&](auto &&self, int x) -> void {
in[x] = idx++;
for (int y: adj[x]) {
adj[y].erase(std::ranges::find(adj[y], x));
for (int s = 0; s <= m; s++) {
dp[y][s] += dp[x][s];
}
for (int s = w[y]; s <= m; s++) {
dp[y][s] += dp[x][s - w[y]];
}
self(self, y);
}
out[x] = idx;
};
dfs(dfs, 0);
auto isAnc = [&](int u, int v) {
return in[u] <= in[v] and in[v] < out[u];
};
int q;
std::cin >> q;
while (q--) {
int x, y;
std::cin >> x >> y;
x--, y--;
auto ndp = dp[x];
if (isAnc(y, x)) {
for (int s = w[y]; s <= m; s++) {
ndp[s] = dp[x][s] - ndp[s - w[y]];
}
}
std::cout << std::accumulate(ndp.begin(), ndp.end(), Z(0)) << "\n";
}
return 0;
}