比赛链接
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 0≤⌊yz⌋≤⌊2xz⌋≤⌊xz⌋=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 ⌊yz⌋≥1 就要求 z ≥ y ≥ 2 x z \geq y \geq 2x z≥y≥2x,与假设不符。
设 k = ⌊ y x ⌋ k = \lfloor \frac{y}{x} \rfloor k=⌊xy⌋,即 k x ≤ y < ( k + 1 ) x kx \leq y \lt (k + 1)x kx≤y<(k+1)x,由题可知 k ≥ 2 k \geq 2 k≥2。对于 ∀ m ∈ Z , m ≥ 0 \forall m \in Z, \ m \geq 0 ∀m∈Z, m≥0,
- 当 k m < l ≤ k m + 1 k^m < l \leq k^{m + 1} km<l≤km+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, ⌊yz⌋≤⌊kxz⌋≤⌊kx(l+1)x⌋=⌊kl+1⌋≤⌊kkm+1+1⌋=km, 于是有 ⌊ z y ⌋ ≤ k m < l = ⌊ z x ⌋ \lfloor \frac{z}{y} \rfloor \leq k^m < l = \lfloor \frac{z}{x} \rfloor ⌊yz⌋≤km<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(x−1,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,aj:⋯ai,aj,ak⋯交换 ai,aj:⋯aj,ai,ak⋯交换 ai,ak:⋯aj,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+1:⋯ai,aq1,aq2,⋯,aqm,aqm+1,aj⋯:⋯ai,aq1,aq2,⋯,aqm,aj,aqm+1⋯:⋯aj,aq1,aq2,⋯aqm,ai,aqm+1⋯:⋯aj,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| 0≤j<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+⋯+xm≤a, xi≥0
对于 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} (m−1j+m−1)(可以看看隔板法)。
那么当前的答案就是 ∑ j = 0 a ( j + m − 1 m − 1 ) \sum\limits_{j = 0}^{a}{j + m - 1 \choose m - 1} j=0∑a(m−1j+m−1)。下面开始化简。
∑ 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=0∑a(m−1j+m−1)=(m−1m−1)+j=1∑a(m−1j+m−1)=(mm)+j=1∑a(m−1j+m−1)=(mm)+(m−1m)+j=2∑a(m−1j+m−1)=(mm+1)+j=2∑a(m−1j+m−1)=⋯=(ma+m).
其中用到了组合数的递推公式
( n m − 1 ) + ( n m ) = ( n + 1 m ) . {n \choose m - 1} + {n \choose m} = {n + 1 \choose m}. (m−1n)+(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=1∏n(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;
}