【2025 华中师范大学-菜鸟杯程序设计竞赛】部分题解

比赛链接

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, 1T105,
  • 1 ≤ n , m , k ≤ 1 0 9 . 1 \leq n, m, k \leq 10^9. 1n,m,k109.

Solution1

一个显然的想法是每次选一个最大的 L \rm\pmb{L} L,即选择边界的 L \rm\pmb{L} L,这就启发我们从边界的 n + ( m − 1 ) n + (m - 1) n+(m1) 个方块组成的 L \rm\pmb{L} L 选起,再转个 18 0 ∘ 180^{\circ} 180 选择另一半的 ( n − 1 ) + ( m − 2 ) (n - 1) + (m - 2) (n1)+(m2) 个方块 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+(m1)sum,说明选了第一个边界 L \rm\pmb{L} L 就可以走人了,花费 k k k 元;
  • 否则 n + ( m − 1 ) < s u m n + (m - 1) < \rm{sum} n+(m1)<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=1c((ni+1)+(mi))=i=1c((n+m)(2i1))=i=1c(n+m)i=1c(2i1)=(n+m)cc2=(n+mc)c.

而我们就是要求最小的 c c c,使得 ( n + m − c ) ⋅ c ≥ s u m (n + m - c) \cdot c \geq sum (n+mc)csum,这可以通过 二分 实现。最终答案就是 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)(m1),即选择了 n m − ( n − m ) − 1 nm - (n - m) - 1 nm(nm)1 个方块,我们只需说明 n m − ( n m − ( n − m ) − 1 ) = ( n − m ) + 1 nm - (nm - (n - m) - 1) = (n - m) + 1 nm(nm(nm)1)=(nm)+1 最多只占总和 n m nm nm 40 % 40\% 40% 即可。

只需证 ( n − m ) + 1 ≤ ⌊ 2 n m 5 ⌋ . (n - m) + 1 \leq \lfloor \frac{2nm}{5} \rfloor. (nm)+152nm.

考虑 ⌊ x ⌋ ≥ x − 1 \lfloor x \rfloor \geq x - 1 xx1,于是我们再换一个目标,想办法证明 ( 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. (nm)+152nm152nm.

化简一下得到 2 n m − 5 ( n − m ) ≥ 10 , 2nm - 5(n - m) \geq 10, 2nm5(nm)10, ( 2 m − 5 ) n + 5 m ≥ 10. (2m - 5)n + 5m \geq 10. (2m5)n+5m10.

由于 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, (2m5)n(235)3=3,5m53=15,

综上得到 ( 2 m − 5 ) n + 5 m ≥ 3 + 15 = 18 ≥ 10. (2m - 5)n + 5m \geq 3 + 15 = 18 \geq 10. (2m5)n+5m3+15=1810.

n ≤ m n \leq m nm 的情况同理可证。

时间复杂度 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+mc)csum 的最小的 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+sum0 后,考虑 Δ = ( 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)24 sum(2nm )24 sum=4(nmsum)>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)24 sum ,c2=2(n+m)+(n+m)24 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)24 sum ,因此 ⌈ c 1 ⌉ ≥ 1 \lceil c_1 \rceil \geq 1 c11,因此最小的 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, 1n2105,
  • − 1 0 9 ≤ h , x 0 , y 0 ≤ 1 0 9 , -10^9 \leq h, x_0, y_0 \leq 10^9, 109h,x0,y0109,
  • − 1 0 9 ≤ x i , y i ≤ 1 0 9 , -10^9 \leq x_i, y_i \leq 10^9, 109xi,yi109,
  • 保证任意两对 ( 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}}) (xik1,yik1) 到某个 ( x ′ , h ) (x', h) (x,h),显然 x ′ = x i k − 1 x' = x_{i_{k - 1}} x=xik1 最佳,那么这一段行走的曼哈顿距离就是 ∣ y i k − 1 − h ∣ , |y_{i_{k - 1}} - h|, yik1h,
  • ( x i k − 1 , h ) (x_{i_{k - 1}}, h) (xik1,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|. xikxik1+yikh∣.

所以总共走过的距离是 ∑ 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=1nyik1h+xikxik1+yikh∣.

整理一下就会变成 ( ∣ 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). (y0h+2(k=1nyikh))+((k=1nxikxik1)yinh).

可以发现,前半括号里都是不变量,而后半括号主要跟结尾的 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 (maxxminx)xexs.

所以只要先预处理好前面的不变量,以及 2   ( m a x x − m i n x ) \rm{2 \ (maxx - minx)} 2 (maxxminx),就可以 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=lri

给定 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, 1T104,
  • 1 ≤ L ≤ R ≤ 1 0 18 . 1 \leq L \leq R \leq 10^{18}. 1LR1018.

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=lri=2(r+l)(rl+1), 不论 l , r l, r l,r 的奇偶, r + l r + l r+l r − l r - l rl 的奇偶性一定是一样的,所以 ( r + l ) ( r − l + 1 ) (r + l)(r - l + 1) (r+l)(rl+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(kN)

那么除了 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=2x1+2x+1, l = x − 1 2 , r = x + 1 2 l = \frac{x - 1}{2}, r = \frac{x + 1}{2} l=2x1,r=2x+1 即可。
  • x x x 是( ≠ 2 k ,   ∀ k ∈ N \neq 2^k, \ \forall k \in N =2k, kN) 的偶数,那么一定有 x = a ⋅ b , x = a \cdot b, x=ab, 不妨设 a a a 为奇数(一定 ≥ 3 \geq 3 3), b b b 为偶数。
    • b > a − 1 2 b > \frac{a - 1}{2} b>2a1,那就构造 ∑ 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=b2a1b+2a1i=2a1(2b)+b=ab=x,
    • 否则就有 b ≤ a − 1 2 < a + 1 2 b \leq \frac{a - 1}{2} < \frac{a + 1}{2} b2a1<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+1b2a+1+b1i= i=2a+1b2a+1+bi (2a+1+b)=b(a+1)+2a+1(2a+1+b)=b(a+1)b=ba=x

综上,除了 2 k ( k ∈ N ) 2^k(k \in N) 2k(kN) 都能用连续自然数的和凑出来。

时间复杂度 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, 1T104,
  • 2 ≤ n ≤ 2 ⋅ 1 0 6 . 2 \leq n \leq 2 \cdot 10^6. 2n2106.

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=(n1)(Dn1+Dn2),

初值条件: 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 n1 种选择来将元素 n n n 放到其他位置。

假设 元素 n n n 被放置在位置 i ∈ [ 1 , n ) i \in [1, n) i[1,n),那么我们就需要考虑剩余 n − 1 n - 1 n1 个元素的错排。这个时候,我们有两种情况需要处理:

  • 元素 i i i 被放置到位置 n n n。这种情况下,剩下的 n − 2 n - 2 n2 个元素错排;
  • 元素 i i i 被放置到其他位置( ≠ n \neq n =n),此时我们需要将剩余的 n − 1 n - 1 n1 个元素错排。

最终答案就是 D n ( n − 1 ) n . \frac{D_n}{(n - 1)^n}. (n1)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. 1XN5105.

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} (N1)1(mod2),说明上面所说的状态是奇数次被访问到的,也就是 A l i c e \rm{Alice} Alice 必胜,因此 N ≡ 0 ( m o d 2 ) N \equiv 0 \pmod{2} N0(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*} ((((((((((a1a2)a1a2)(a3a4)a1a2)(a3a4))(a3a4))(a5a6)a1a2)(a3a4))(a3a4))(a5a6))(a5a6))(a7a8)

时间复杂度 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  chosenwi)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, 2n,m,q5000,
  • 1 ≤ w i ≤ m , 1 \leq w_i \leq m, 1wim,
  • 1 ≤ x , y ≤ n , 1 \leq x, y \leq n, 1x,yn,
  • 不保证 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][swy], ∀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=0mdp[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][swx], 其中 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][swx] 表示已经 选完 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][swx], 同时我们发现 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[swx]. 最后输出 ∑ s = 0 m n d p [ s ] . \sum\limits_{s = 0}^{m}\rm{ndp[s]}. s=0mndp[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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值