【牛客练习赛 135 A-D 题解】

比赛链接

A. 小柒的数字

Solution

严格证明一下只有 z ∈ [ 1 , x ) z \in [1, x) z[1,x) 时是满足条件的。

  • z ∈ [ 1 , x ) z \in [1, x) z[1,x) 时,显然 0 ≤ ⌊ z y ⌋ ≤ ⌊ z 2 x ⌋ ≤ ⌊ z x ⌋ = 0 0 \leq \lfloor \frac{z}{y} \rfloor \leq \lfloor \frac{z}{2x} \rfloor \leq \lfloor \frac{z}{x} \rfloor = 0 0yz2xzxz=0,因此有 ⌊ z y ⌋ = ⌊ z x ⌋ = 0 \lfloor \frac{z}{y} \rfloor = \lfloor \frac{z}{x} \rfloor = 0 yz=xz=0
  • z ∈ [ x , 2 x ) z \in [x, 2x) z[x,2x) 时,显然 ⌊ z x ⌋ = 1 > 0 = ⌊ z y ⌋ \lfloor \frac{z}{x} \rfloor = 1 > 0 = \lfloor \frac{z}{y} \rfloor xz=1>0=yz,因为如果 ⌊ z y ⌋ ≥ 1 \lfloor \frac{z}{y} \rfloor \geq 1 yz1 就要求 z ≥ y ≥ 2 x z \geq y \geq 2x zy2x,与假设不符。

k = ⌊ y x ⌋ k = \lfloor \frac{y}{x} \rfloor k=xy,即 k x ≤ y < ( k + 1 ) x kx \leq y \lt (k + 1)x kxy<(k+1)x,由题可知 k ≥ 2 k \geq 2 k2。对于 ∀ m ∈ Z ,   m ≥ 0 \forall m \in Z, \ m \geq 0 mZ, m0

  • k m < l ≤ k m + 1 k^m < l \leq k^{m + 1} km<lkm+1 时,对于 ∀ z ∈ [ l x , ( l + 1 ) x ) \forall z \in [lx, (l + 1)x) z[lx,(l+1)x),均有 ⌊ z x ⌋ = l > k m \lfloor \frac{z}{x} \rfloor = l > k^m xz=l>km,而 ⌊ z y ⌋ ≤ ⌊ z k x ⌋ ≤ ⌊ ( l + 1 ) x k x ⌋ = ⌊ l + 1 k ⌋ ≤ ⌊ k m + 1 + 1 k ⌋ = k m , \lfloor \frac{z}{y} \rfloor \leq \lfloor \frac{z}{kx} \rfloor \leq \lfloor \frac{(l + 1)x}{kx} \rfloor = \lfloor \frac{l + 1}{k} \rfloor \leq \lfloor \frac{k^{m + 1} + 1}{k} \rfloor = k^m, yzkxzkx(l+1)x=kl+1kkm+1+1=km, 于是有 ⌊ z y ⌋ ≤ k m < l = ⌊ z x ⌋ \lfloor \frac{z}{y} \rfloor \leq k^m < l = \lfloor \frac{z}{x} \rfloor yzkm<l=xz

于是命题得证,即只有 z ∈ [ 1 , x ) z \in [1, x) z[1,x) 满足 ⌊ z y ⌋ = ⌊ z x ⌋ \lfloor \frac{z}{y} \rfloor = \lfloor \frac{z}{x} \rfloor yz=xz

所以最终答案就是 min ⁡ ( x − 1 , n ) \min(x - 1, n) min(x1,n) n n n 是题目特定的限制 z ∈ [ 1 , n ] z \in [1, n] z[1,n]

时间复杂度 O ( T ) O(T) O(T)

C++ Code

#include <bits/stdc++.h>

void solve() {
    int x, y, n;
    std::cin >> x >> y >> n;
    std::cout << std::min(x - 1, n) << "\n";
}

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    
    int T = 1;
    std::cin >> T;
    
    while (T--) {
        solve();
    }
    
    return 0;
}

B. 小柒的逆序对(一)

Solution

本题要求动态维护逆序对的奇偶性,这其实涉及到一个线性代数的结论。下面开始证明:对于任意一个排列,交换任意两个下标不同的数,逆序对奇偶性一定发生变化

设排列是 ⋯ a i , a j ⋯ \cdots a_i, a_j\cdots ai,aj。由排列的性质,不妨设 a i < a j a_i < a_j ai<aj,那么交换 a i , a j a_i, a_j ai,aj 后,其余位置的逆序对都没发生变化,仅仅是增加了一个 ( a j , a i ) (a_j, a_i) (aj,ai) 逆序对,奇偶性改变。 a i > a j a_i > a_j ai>aj 同理,只是逆序对减少一个。

设排列是 ⋯ a i , a k , a j ⋯ \cdots a_i, a_k, a_j \cdots ai,ak,aj。由上一段的结论,邻项交换一定改变逆序对的奇偶,那么对于任意一个使得其变为 ⋯ a j , a k , a i ⋯ \cdots a_j, a_k, a_i \cdots aj,ak,ai 的方案,只要这个方案始终保持邻项交换,逆序对奇偶性都会换一次变一次。现在我们考虑下面的过程。

原排列: ⋯ a i , a k , a j ⋯ 交换  a k , a j : ⋯ a i , a j , a k ⋯ 交换  a i , a j : ⋯ a j , a i , a k ⋯ 交换  a i , a k : ⋯ a j , a k , a i ⋯ \begin{align*} 原排列:\cdots a_i, a_k, a_j \cdots \\ 交换 \ a_k, a_j: \cdots a_i, a_j, a_k \cdots \\ 交换 \ a_i, a_j:\cdots a_j, a_i, a_k \cdots \\ 交换 \ a_i, a_k:\cdots a_j, a_k, a_i \cdots \end{align*} 原排列:ai,ak,aj交换 ak,ajai,aj,ak交换 ai,ajaj,ai,ak交换 ai,akaj,ak,ai

一共进行了三次邻项交换,这说明要交换 ⋯ a i , a k , a j ⋯ \cdots a_i, a_k, a_j \cdots ai,ak,aj 中的 a i , a j a_i, a_j ai,aj,逆序对奇偶性改变。

上面我们证明了 a i , a j a_i, a_j ai,aj 之间有 1 1 1 个数 a k a_k ak 的情况,下面进行数学归纳法。

a i , a j a_i, a_j ai,aj 之间有 m m m 个数,排列为 ⋯ a i , a p 1 , a p 2 , ⋯   , a p m ,   a j ⋯ \cdots a_i, a_{p_1}, a_{p_2}, \cdots, a_{p_m}, \ a_j \cdots ai,ap1,ap2,,apm, aj,此时交换 a i , a j a_i, a_j ai,aj,逆序对数量改变。下证对于 m + 1 m + 1 m+1 的情况也成立。

a i , a j a_i, a_j ai,aj 之间有 m + 1 m + 1 m+1 个数,排列为 ⋯ a i , a q 1 , a q 2 , ⋯   , a q m , a q m + 1 , a j ⋯ \cdots a_i, a_{q_1}, a_{q_2}, \cdots, a_{q_m}, a_{q_{m + 1}}, a_j \cdots ai,aq1,aq2,,aqm,aqm+1,aj,下面是交换过程。

原排列 : ⋯ a i , a q 1 , a q 2 , ⋯   , a q m , a q m + 1 , a j ⋯ 交换 a q m + 1 , a j : ⋯ a i , a q 1 , a q 2 , ⋯   , a q m , a j , a q m + 1 ⋯ 交换 a j , a i : ⋯ a j , a q 1 , a q 2 , ⋯ a q m , a i , a q m + 1 ⋯ 交换 a i , a q m + 1 : ⋯ a j , a q 1 , a q 2 , ⋯ a q m , a q m + 1 , a i ⋯ \begin{align*} 原排列&:\cdots a_i, a_{q_1}, a_{q_2}, \cdots, a_{q_m}, a_{q_{m + 1}}, a_j \cdots \\ 交换 a_{q_{m + 1}}, a_j&:\cdots a_i, a_{q_1}, a_{q_2}, \cdots, a_{q_m}, a_j, a_{q_{m + 1}} \cdots \\ 交换 a_j, a_i&:\cdots a_j, a_{q_1}, a_{q_2}, \cdots a_{q_m}, a_i, a_{q_{m + 1}} \cdots \\ 交换 a_i, a_{q_{m + 1}}&:\cdots a_j, a_{q_1}, a_{q_2}, \cdots a_{q_m}, a_{q_{m + 1}}, a_i \cdots \end{align*} \\ 原排列交换aqm+1,aj交换aj,ai交换ai,aqm+1ai,aq1,aq2,,aqm,aqm+1,ajai,aq1,aq2,,aqm,aj,aqm+1aj,aq1,aq2,aqm,ai,aqm+1aj,aq1,aq2,aqm,aqm+1,ai

也可以看成三步(因为根据假设第二大步的交换会使得奇偶性改变),所以由数学归纳法得到:

对于任意一个排列,交换任意两个下标不同的数,逆序对奇偶性一定发生变化。

因此只要判断交换的两个下标 i , j i, j i,j 是否相同即可。

时间复杂度 O ( n + q ) O(n + q) O(n+q)

C++ Code

#include <bits/stdc++.h>

using i64 = long long;

template<class T>
std::istream &operator>>(std::istream &is, std::vector<T> &v) {
    for (auto &x: v) {
        is >> x;
        x--;
    }
    return is;
}

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    
    int n, q;
    i64 m;
    std::cin >> n >> m >> q;

    std::vector<int> a(n);
    std::cin >> a;

    std::array<std::string, 2> ans{"even", "odd"};

    while (q--) {
        int i, j;
        std::cin >> i >> j;
        m ^= (i != j);
        std::cout << ans[m & 1] << "\n";
    }
    
    return 0;
}

C. 小柒的逆序对(2)

Solution

由于这题只有小写字母,因此我们(在正常的 a b c d e f g ⋯ abcdefg\cdots abcdefg 字典序下)可以记录每种字符对的数量,例如 c a , a b , z h ca, ab, zh ca,ab,zh 等,我们把这些信息记录到一个 ∣ ∑ ∣ 2 |\sum|^2 2 的数组 r e v rev rev 里,其中 i i i 代表字符 i + i + i+‘a’。

接下来对于每个询问给出的长度为 ∣ ∑ ∣ |\sum| 的新字典序 o r d ord ord,考虑 O ( ∣ ∑ ∣ 2 ) O(|\sum|^2) O(2) 遍历 o r d ord ord,对于每一对 ( i , j ) (i, j) (i,j) 满足 0 ≤ j < i < ∣ ∑ ∣ 0 \leq j < i < |\sum| 0j<i<,首先要删去 r e v [ i ] [ j ] rev[i][j] rev[i][j],因为原来正常的 a b c d e f g ⋯ abcdefg\cdots abcdefg 字典序下, ( i + ‘ a ’ , j + ‘ a ’ ) (i + ‘a’, j + ‘a’) (i+a,j+a) 是逆序对,但是现在不一定是,所以先删去;接着加上 r e v [ o r d [ i ] − ‘ a ’ ] [ o r d [ j ] − ‘ a ’ ] rev[ord[i] - ‘a’][ord[j] - ‘a’] rev[ord[i]a][ord[j]a],表示 ( o r d [ i ] , o r d [ j ] ) (ord[i], ord[j]) (ord[i],ord[j]) 是新的逆序对。

时间复杂度 O ( ( n + q ) ∣ ∑ ∣ 2 ) O((n + q)|\sum|^2) O((n+q)2)

  • 其中 ∣ ∑ ∣ |\sum| 为字符集的大小,本题中为 26 26 26

C++ Code

#include <bits/stdc++.h>

using i64 = long long;

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    
    int n, q;
    std::cin >> n >> q;

    std::string s;
    std::cin >> s;

    std::array<int, 26> pre{};
    std::array<std::array<i64, 26>, 26> rev{};
    for (int i = 0; i < n; i++) {
        int c = s[i] - 'a';
        for (int j = 0; j < 26; j++) {
            rev[j][c] += pre[j];
        }
        pre[c]++;
    }
    i64 sum = 0;
    for (int i = 1; i < 26; i++) {
        for (int j = 0; j < i; j++) {
            sum += rev[i][j];
        }
    }

    while (q--) {
        std::string ord;
        std::cin >> ord;

        i64 res = sum;
        for (int i = 1; i < 26; i++) {
            for (int j = 0; j < i; j++) {
                res -= rev[i][j];
                res += rev[ord[i] - 'a'][ord[j] - 'a'];
            }
        }
        std::cout << res << "\n";
    }    
    
    return 0;
}

D. 小柒分糖果

Solution

n n n 个糖果的挑选是独立的,因此我们可以分开算,最后把这 n n n 个糖果的分配情况乘起来。

对于其中一种糖果有数量 a a a,现在要分给 m m m 个人。设 x i x_i xi 表示第 i i i 个人的糖果数,要求满足

x 1 + x 2 + ⋯ + x m ≤ a ,   x i ≥ 0 x_1 + x_2 + \cdots + x_m \leq a, \ x_i \geq 0 x1+x2++xma, xi0

对于 j ∈ [ 0 , a ] j \in [0, a] j[0,a] x 1 + x 2 + ⋯ + x m = j x_1 + x_2 + \cdots + x_m = j x1+x2++xm=j 解的数量为 ( j + m − 1 m − 1 ) {j + m - 1 \choose m - 1} (m1j+m1)(可以看看隔板法)。

那么当前的答案就是 ∑ j = 0 a ( j + m − 1 m − 1 ) \sum\limits_{j = 0}^{a}{j + m - 1 \choose m - 1} j=0a(m1j+m1)。下面开始化简。

∑ j = 0 a ( j + m − 1 m − 1 ) = ( m − 1 m − 1 ) + ∑ j = 1 a ( j + m − 1 m − 1 ) = ( m m ) + ∑ j = 1 a ( j + m − 1 m − 1 ) = ( m m ) + ( m m − 1 ) + ∑ j = 2 a ( j + m − 1 m − 1 ) = ( m + 1 m ) + ∑ j = 2 a ( j + m − 1 m − 1 ) = ⋯ = ( a + m m ) . \begin{align*} \sum\limits_{j = 0}^{a}{j + m - 1 \choose m - 1} &= {m - 1 \choose m - 1} + \sum\limits_{j = 1}^{a}{j + m - 1 \choose m - 1} \\ &= {m \choose m} + \sum\limits_{j = 1}^{a}{j + m - 1 \choose m - 1} \\ &= {m \choose m} + {m \choose m - 1} + \sum\limits_{j = 2}^{a}{j + m - 1 \choose m - 1} \\ &= {m + 1 \choose m} + \sum\limits_{j = 2}^{a}{j + m - 1 \choose m - 1} \\ &= \cdots \\ &= {a + m \choose m}. \end{align*} j=0a(m1j+m1)=(m1m1)+j=1a(m1j+m1)=(mm)+j=1a(m1j+m1)=(mm)+(m1m)+j=2a(m1j+m1)=(mm+1)+j=2a(m1j+m1)==(ma+m).

其中用到了组合数的递推公式

( n m − 1 ) + ( n m ) = ( n + 1 m ) . {n \choose m - 1} + {n \choose m} = {n + 1 \choose m}. (m1n)+(mn)=(mn+1).

考虑每一个 a i ,   i ∈ [ 1 , n ] a_i, \ i \in [1, n] ai, i[1,n],最后的答案就是

∏ i = 1 n ( a i + m m ) . \prod\limits_{i = 1}^{n}{a_i + m \choose m}. i=1n(mai+m).

时间复杂度 O ( n ) O(n) O(n)

C++ Code

#include <bits/stdc++.h>

using i64 = long long;

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 = 1000000007;
using Z = MInt<P>;

struct Comb {
    int n;
    std::vector<Z> _fac;
    std::vector<Z> _invfac;
    std::vector<Z> _inv;
    
    Comb() : n{0}, _fac{1}, _invfac{1}, _inv{0} {}
    Comb(int n) : Comb() {
        init(n);
    }
    void init(int m) {
        m = std::min(m, Z::getMod() - 1);
        if (m <= n) return;
        _fac.resize(m + 1);
        _invfac.resize(m + 1);
        _inv.resize(m + 1);
        for (int i = n + 1; i <= m; i += 1) {
            _fac[i] = _fac[i - 1] * i;
        }
        _invfac[m] = _fac[m].inv();
        for (int i = m; i > n; i -= 1) {
            _invfac[i - 1] = _invfac[i] * i;
            _inv[i] = _invfac[i] * _fac[i - 1];
        }
        n = m;
    }
    Z fac(int m) {
        if (m > n) init(2 * m);
        return _fac[m];
    }
    Z invfac(int m) {
        if (m > n) init(2 * m);
        return _invfac[m];
    }
    Z inv(int m) {
        if (m > n) init(2 * m);
        return _inv[m];
    }
    Z binom(int n, int m) {
        if (n < m || m < 0) {
            return 0;
        }
        return fac(n) * invfac(m) * invfac(n - m);
    }
    Z Lucas(i64 n, i64 m, int p) {
        if (n < p and m < p) {
            return binom(n, m);
        }
        return Lucas(n / p, m / p, p) * binom(n % p, m % p);
    }
    Z Lucas(i64 n, i64 m) {
        if (n < Z::getMod() and m < Z::getMod()) {
            return binom(n, m);
        }
        return Lucas(n / Z::getMod(), m / Z::getMod()) * binom(n % Z::getMod(), m % Z::getMod());   
    }
    Z perm(int n, int m) {
        if (n < m or m < 0) {
            return 0;
        }
        return fac(n) * invfac(n - m);
    }
} comb;

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    
    int n, m;
    std::cin >> n >> m;

    Z ans = 1;
    for (int i = 0; i < n; i++) {
        int a;
        std::cin >> a;
        ans *= comb.binom(a + m, m);
    }
    std::cout << ans << "\n";
    
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值