CodeJam 2021 qualified
Reversort Engineering
本例涉及到摊还分析,动态规划中有一个很常见的相关技巧就是未来费用提前计算
首先很容易想到对 [ 1 ⋯ n ] [1\cdots n] [ 1 ⋯ n ] 的排序代价 C C C 满足
n − 1 ⩽ C ⩽ n ( n + 1 ) 2 − 1 n-1 \leqslant C \leqslant \frac{n(n+1)}{2} - 1
n − 1 ⩽ C ⩽ 2 n ( n + 1 ) − 1
可以反着来构造,最开始令序列有序 a = { 1 , 2 , ⋯ n } a = \{1, 2, \cdots n\} a = { 1 , 2 , ⋯ n }
d ( i ) d(i) d ( i ) 表示从 i i i 出发,找到最小元素下标为 j j j ,需要走的距离,即 len [ i ⋯ j ] \text{len}[i \cdots j] len [ i ⋯ j ]
我们是反着构造的,所以实际上 d ( i ) = len [ i ⋯ j ] d(i)=\text{len}[i \cdots j] d ( i ) = len [ i ⋯ j ] ,j j j 为从 i → n i \to n i → n 最大元素的下标
初始阶段,令 C ← C − ( n − 1 ) d ( i ) = 1 C \leftarrow C-(n-1) \quad d(i) = 1 C ← C − ( n − 1 ) d ( i ) = 1
如果 C > 0 C > 0 C > 0 ,就说明 len [ i ⋯ j ] > 1 \text{len}[i \cdots j] > 1 len [ i ⋯ j ] > 1 ,产生了额外代价
于是可以贪心构造,如果在排序的某个阶段,∀ i ∈ [ n − 2 → 0 ] \forall i \in [n-2 \to 0] ∀ i ∈ [ n − 2 → 0 ]
[ i ⋯ j ] [i \cdots j] [ i ⋯ j ] ,让 a j a_j a j 为最小,a i a_i a i 为第 2 2 2 小
反着构造就是 [ i ⋯ j ] [i \cdots j] [ i ⋯ j ] ,a i a_i a i 为当前最小,a j a_j a j 为第 2 2 2 小,反转 [ i ⋯ j ] [i \cdots j] [ i ⋯ j ]
此时代价就是每一段 [ i ⋯ j ] [i \cdots j] [ i ⋯ j ] 能够取到的最坏代价 C ′ C' C ′
for ∀ i ∈ [ n − 2 → 0 ] \textbf{for} \ \forall i \in [n-2 \to 0] for ∀ i ∈ [ n − 2 → 0 ] ,i i i 能往前走的最长距离是 max d ( i ) = n − 1 − i \max d(i) = n-1-i max d ( i ) = n − 1 − i
只要 C > 0 C > 0 C > 0 ,执行
for [ 1 ⋯ n − 1 − i ] : d ( i ) + = 1 , C − = 1 \textbf{for} [1 \cdots n-1-i]: \ d(i) += 1, \ C -= 1
for [ 1 ⋯ n − 1 − i ] : d ( i ) + = 1 , C − = 1
即让 i i i 尽可能往前走,贪心地让 [ i ⋯ n − 1 ] [i\cdots n-1] [ i ⋯ n − 1 ] 中最小的元素尽可能一直位于 a n − 1 a_{n-1} a n − 1 处
d ( i ) d(i) d ( i ) 初始时为 1 1 1 ,max d ( i ) = n − i \max d(i) = n-i max d ( i ) = n − i
此时需要反转的区间是 [ i , j ] = [ i ⋯ i + d ( i ) − 1 ] [i, j] = [i \cdots i+d(i)-1] [ i , j ] = [ i ⋯ i + d ( i ) − 1 ]
反转 [ i , j ] = [ i , i + d ( i ) − 1 ] [i, j] = [i, i+d(i)-1] [ i , j ] = [ i , i + d ( i ) − 1 ] ,在第 i − 1 i-1 i − 1 轮迭代中a i − 1 < a [ i ⋯ j ] , 对于区间 [ i − 1 ⋯ j ] a i − 1 为最小, a j 第 2 小 \begin{gathered}
a_{i-1} < a[i \cdots j], \quad \text{对于区间} [i-1\cdots j] \\
a_{i-1} \text{为最小,} \quad a_{j} \text{第 2 小}
\end{gathered}
a i − 1 < a [ i ⋯ j ] , 对于区间 [ i − 1 ⋯ j ] a i − 1 为最小 , a j 第 2 小
C > 0 C > 0 C > 0 ,第 i − 1 i-1 i − 1 阶段,[ i ⋯ n − 1 ] [i\cdots n-1] [ i ⋯ n − 1 ] 中最小的元素尽可能一直位于 a n − 1 a_{n-1} a n − 1 处
又因为 a i − 1 < ∀ a [ i ⋯ n − 1 ] a_{i-1} < \forall \ a[i \cdots n-1] a i − 1 < ∀ a [ i ⋯ n − 1 ]
a i − 1 a_{i-1} a i − 1 最小,a n − 1 a_{n-1} a n − 1 第 2 2 2 小,反转 a [ i − 1 , n − 1 ] a[i-1, n-1] a [ i − 1 , n − 1 ] 即可
反着迭代 n − 1 n-1 n − 1 次,即可得到原序列
最后对于不足以填充 [ i ⋯ n − 1 ] [i \cdots n-1] [ i ⋯ n − 1 ] 的 d ( i ) d(i) d ( i ) ,也不会影响结果
因为此时 [ i ⋯ i + d ( i ) − 1 ] [i \cdots i+d(i)-1] [ i ⋯ i + d ( i ) − 1 ] 一定是最后一次迭代的区间,反转之后对应原序列第 i i i 次迭代
恰好满足 a i + d ( i ) − 1 a_{i+d(i)-1} a i + d ( i ) − 1 是 [ i ⋯ i + d ( i ) − 1 ] [i \cdots i+d(i)-1] [ i ⋯ i + d ( i ) − 1 ] 区间最小元素,对 i − 1 i-1 i − 1 及之前的迭代没有影响
因为前面的元素 a [ 0 ⋯ i − 1 ] < a [ i ⋯ n ] a[0 \cdots i-1] < a[i \cdots n] a [ 0 ⋯ i − 1 ] < a [ i ⋯ n ] ,∀ j ∈ [ 0 , i − 1 ] , d ( j ) = 1 \forall j \in [0, i-1], d(j) = 1 ∀ j ∈ [ 0 , i − 1 ] , d ( j ) = 1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 using namespace std; const int maxn = 100 + 10; int n, C; void solve () { vector<int> d(n-1, 1); C -= (n-1); for (int i = n-2; i >= 0; i--) { for (int _ = 1; _ <= n-1-i; _ ++) { if (C > 0) { C -= 1, d[i] += 1; } } } vector<int> a(n); for (int i = 0; i < n; i++) a[i] = i+1; for (int i = n-2; i >= 0; i--) { reverse(a.begin()+i, a.begin()+i+d[i]); } for (auto x : a) printf ("%d " , x); printf ("\n" ); } int main () { freopen("input.txt" , "r" , stdin); int T, kase = 0; cin >> T; while (T--) { printf ("Case #%d: " , ++kase); cin >> n >> C; // impossible int lo = n-1, hi = n*(n+1) / 2 - 1; if (C < lo || C > hi) { puts("IMPOSSIBLE" ); continue ; } // solve solve(); } }
数学基础
筛法求素数
朴素筛法
1 2 3 4 5 6 7 8 9 10 11 vector<int> primes; bool st[maxn]; // init st[...] = 0 // st = true 表示这个数是合数 void get_primes(int n) { for (int i = 2; i <= n; i++) { if (st[i]) continue ; primes.push_back(i); for (int j = 2*i; j <= n; j += i) st[j] = true ; } }
线性筛法
1 2 3 4 5 6 7 8 9 10 11 12 vector<int> primes; bool st[maxn]; void get_primes () { for (int i = 2; i <= n; i++) { if (!st[i]) primes.push_back(i); for (int j = 0; primes[j] <= n/i; j++) { st[primes[j] * i] = true ; if (i % primes[j] == 0) break ; } } }
约数
试除法分解素因子
1 2 3 4 5 6 7 8 9 10 11 12 13 vector<PII> div; void divide(int x) { for (int i = 2; i <= x/i; i++) { if (x % i == 0) { int s = 0; while (x % i == 0) s++, x /= i; // i^s div.push_back({i, s}); } } if (x > 1) div.push_back({x, 1}); // x^1 }
阶乘分解
1 2 3 4 5 inline int get(int n, int p) { int s = 0; while (n) s += n/p, n /= p; return s; }
组合数的几种求法
递推求组合数
( n k ) = ( n − 1 k ) + ( n − 1 k − 1 ) \dbinom{n}{k} = \binom{n-1}{k} + \binom{n-1}{k-1}
( k n ) = ( k n − 1 ) + ( k − 1 n − 1 )
1 2 3 4 5 6 7 8 9 10 11 const int maxn = 1e4 + 10; const int mod = 100000007; int c[maxn][maxn], n; void calc () { c[0][0] = 1; for (int i = 1; i < n; i++) for (int j = 0; j <= i; j++) { if (!j) c[i][j] = 1; else c[i][j] = (c[i-1][j] + c[i-1][j-1]) % mod; } }
乘法逆元求组合数
乘法逆元
若 ( b , m ) = 1 (b, m) = 1 ( b , m ) = 1 ,并且 b ∣ a b \mid a b ∣ a ,那么 a / b = a ⋅ ( b − 1 m o d m ) a / b = a \cdot (b^{-1} \bmod m) a / b = a ⋅ ( b − 1 m o d m )
( b − 1 m o d m ) (b^{-1} \bmod m) ( b − 1 m o d m ) 即为 b b b 的 m o d m \bmod \ m m o d m 的乘法逆元
注意前提,( b , m ) = 1 (b, m) = 1 ( b , m ) = 1 ,即 b , m b, m b , m 互素
a / b = a ⋅ b − 1 ≡ a / b ⋅ ( b ⋅ b − 1 ) ( m o d m ) ⇒ b ⋅ b − 1 ≡ 1 ( m o d m ) \begin{aligned}
a/b &= a \cdot b^{-1} \equiv a/b \cdot (b \cdot b^{-1}) (\bmod \ m) \\
&\Rightarrow b \cdot b^{-1} \equiv 1 (\bmod \ m)
\end{aligned}
a / b = a ⋅ b − 1 ≡ a / b ⋅ ( b ⋅ b − 1 ) ( m o d m ) ⇒ b ⋅ b − 1 ≡ 1 ( m o d m )
algorithm \textbf{algorithm} algorithm
于是在求组合数,求 a / b a / b a / b 并且对一个比较大的素数 p p p 取模的时候,注意一定要 ( b , p ) = 1 (b, p) = 1 ( b , p ) = 1
a / b ( m o d p ) ⟹ a ⋅ b − 1 ( m o d p ) a / b \ (\bmod \ p) \Longrightarrow a \cdot b^{-1} \ (\bmod \ p) a / b ( m o d p ) ⟹ a ⋅ b − 1 ( m o d p ) ,以下省略 ( m o d p ) (\bmod \ p) ( m o d p )
除以 b b b 等价于乘以 b b b 的乘法逆元 b − 1 b^{-1} b − 1
求b b b 的乘法逆元 b − 1 b^{-1} b − 1 ,实际上就是求 b x ≡ 1 ( m o d p ) bx \equiv 1 (\bmod \ p) b x ≡ 1 ( m o d p ) 的解
该式的唯一解为x ≡ b ϕ ( p ) − 1 ( m o d p ) ≡ b p − 2 ( m o d p ) b − 1 = qpow ( b , p − 2 ) 可以用快速幂迅速求出 \begin{aligned}
x &\equiv b^{\phi (p) - 1} \ (\bmod \ p) \equiv b^{p-2} \ (\bmod \ p) \\
b^{-1} &= \text{qpow}(b, p-2) \quad \text{可以用快速幂迅速求出}
\end{aligned}
x b − 1 ≡ b ϕ ( p ) − 1 ( m o d p ) ≡ b p − 2 ( m o d p ) = qpow ( b , p − 2 ) 可以用快速幂迅速求出
证明
Euler-Fermat定理 设 ( a , m ) = 1 (a, m) = 1 ( a , m ) = 1 ,则有a ϕ ( m ) ≡ 1 ( m o d m ) a^{\phi(m)} \equiv 1 (\bmod \ m)
a ϕ ( m ) ≡ 1 ( m o d m )
设 A = { b 1 , b 2 ⋯ b ϕ ( m ) } A = \{b_1, b_2 \cdots b_{\phi (m)}\} A = { b 1 , b 2 ⋯ b ϕ ( m ) } 为模 m m m 的一个简化剩余系,因为 ( a , m ) = 1 (a, m) = 1 ( a , m ) = 1 ,所以
B = { a b 1 , a b 2 , ⋯ a b ϕ ( m ) } B= \{ab_1, ab_2, \cdots ab_{\phi (m)}\} B = { a b 1 , a b 2 , ⋯ a b ϕ ( m ) } 也是模 m m m 的一个简化剩余系,于是 ∏ A \prod A ∏ A 和 ∏ B \prod B ∏ B 同余b 1 b 2 ⋯ b ϕ ( m ) ≡ a ϕ ( m ) b 1 b 2 ⋯ b ϕ ( m ) ( m o d m ) b_1b_2 \cdots b_{\phi (m)} \equiv a^{\phi (m)} b_1b_2 \cdots b_{\phi (m)} (\bmod \ m)
b 1 b 2 ⋯ b ϕ ( m ) ≡ a ϕ ( m ) b 1 b 2 ⋯ b ϕ ( m ) ( m o d m )
每一个 b i b_i b i 都与 m m m 互素,即可得到定理
如果一个素数 p p p 不能整除 a a a ,那么a p − 1 ≡ 1 ( m o d p ) a^{p-1} \equiv 1 (\bmod \ p)
a p − 1 ≡ 1 ( m o d p )
根据欧拉函数 ϕ ( p ) = p − 1 \phi (p) = p-1 ϕ ( p ) = p − 1 即可得到
对任一整数 a a a 与任一素数 p p p ,我们有a p ≡ a ( m o d p ) a^{p} \equiv a(\bmod \ p)
a p ≡ a ( m o d p )
这也是很显然的,因为 p ∤ a p \not | \ a p ∣ a ,那么这个结论就是 2 2 2 ,如果 p ∣ a p \mid a p ∣ a
那么 a p , a a^{p}, a a p , a 都同于 0 m o d p 0 \bmod p 0 m o d p
如果 ( a , m ) = 1 (a, m) = 1 ( a , m ) = 1 ,那么一次同余式 a x ≡ b ( m o d m ) ax \equiv b \ (\bmod \ m) a x ≡ b ( m o d m ) 的唯一解x ≡ b a ϕ ( m ) − 1 ( m o d m ) x \equiv ba^{\phi (m) - 1} (\bmod \ m)
x ≡ b a ϕ ( m ) − 1 ( m o d m )
根据 Euler-Fermat 定理,上式满足 a x ≡ b ( m o d m ) ax \equiv b \ (\bmod \ m) a x ≡ b ( m o d m ) ,又因为 ( a , m ) = 1 (a, m) = 1 ( a , m ) = 1
所以这个解对于 m o d m \bmod \ m m o d m 是唯一的
逆元表
当取模数很大的时候,需要利用递推式,线性求出 [ 1 ⋯ n ] [1\cdots n] [ 1 ⋯ n ] 中每个数在 m o d p \bmod \ p m o d p 意义下的乘法逆元
( p m o d i ) + ⌊ p i ⌋ ⋅ i = p (p \bmod \ i) + \left\lfloor \frac{p}{i} \right\rfloor \cdot i = p
( p m o d i ) + ⌊ i p ⌋ ⋅ i = p
两边同时对 p p p 取 m o d \bmod m o d
⌊ p i ⌋ ⋅ i ≡ − ( p m o d i ) \left\lfloor \frac{p}{i} \right\rfloor \cdot i \equiv -(p \bmod \ i)
⌊ i p ⌋ ⋅ i ≡ − ( p m o d i )
从而推出
i − 1 ≡ − ⌊ p i ⌋ ⋅ ( p m o d i ) − 1 m o d p i^{-1} \equiv -\left\lfloor \frac{p}{i} \right\rfloor \cdot (p \bmod \ i)^{-1} \bmod \ p
i − 1 ≡ − ⌊ i p ⌋ ⋅ ( p m o d i ) − 1 m o d p
注意 − ⌊ p i ⌋ m o d p - \displaystyle \left\lfloor \frac{p}{i} \right\rfloor \bmod p − ⌊ i p ⌋ m o d p 一般写成 ( p − ⌊ p i ⌋ ) m o d p \displaystyle \left(p - \displaystyle \left\lfloor \frac{p}{i} \right\rfloor \right) \bmod p ( p − ⌊ i p ⌋ ) m o d p
1 2 3 4 5 6 7 const int maxn = 1e6 + 10; ll inv[maxn]; void init_inv () { inv[1] = 1; for (int i = 2; i < n; i++) inv[i] = (mod - mod/i) * inv[mod % i] % mod; }
预处理逆元求组合数
( x y ) = x ! y ! ( x − y ) ! m o d p \binom{x}{y} = \frac{x!}{y!(x-y)!} \ \bmod \ p
( y x ) = y ! ( x − y ) ! x ! m o d p
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const int maxn = 1e5 + 10, N = 1e5; const int mod = 10000007; ll fac[maxn], infac[maxn]; void calc () { fac[0] = infac[0] = 1; for (int i = 1; i <= N; i++) { fac[i] = fac[i-1] * i % mod; infac[i] = infac[i-1] * ksm(i, mod-2, mod) % mod; } } int C(int x, int y) { ll ans = fac[x] * infac[y] * infac[x-y] % mod; return ans; }
也可以直接利用组合数公式,预处理阶乘,逆元
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ll inv[maxn], fac[maxn], infac[maxn]; inline ll mul(ll a, ll b) { return a * b % mod; } inline ll mul(ll a, ll b, ll c) { return a * b % mod * c % mod; } void pre () { inv[0] = inv[1] = 1; for (int i = 2; i < maxn; i++) inv[i] = (mod - mod/i) * inv[mod%i] % mod; fac[0] = infac[0] = 1; for (int i = 1; i < maxn; i++) fac[i] = mul(fac[i-1], i); for (int i = 1; i < maxn; i++) infac[i] = mul(infac[i-1], inv[i]); } inline int C(int x, int y) { if (x < y) return 0; return mul(fac[x], infac[x-y], infac[y]); }
Lucas 定理求组合数
Lucas 定理
对于素数 p p p
( n m ) m o d p = ( ⌊ n / p ⌋ ⌊ m / p ⌋ ) ( n m o d p m m o d p ) m o d p \binom{n}{m} \bmod \ p = \binom{\lfloor n/p \rfloor}{\lfloor m/p \rfloor} \binom{n \bmod \ p}{m \bmod p} \bmod p
( m n ) m o d p = ( ⌊ m / p ⌋ ⌊ n / p ⌋ ) ( m m o d p n m o d p ) m o d p
证明
构造生成函数 g ( x ) = ( 1 + x ) p g(x) = (1 + x)^p g ( x ) = ( 1 + x ) p ,有如下式子成立
( 1 + x ) p ≡ 1 + x p ( m o d p ) (1 + x)^p \equiv 1 + x^p (\bmod \ p)
( 1 + x ) p ≡ 1 + x p ( m o d p )
根据二项式展开,∀ k ∈ [ 1 , p − 1 ] , p ∣ ( p k ) \forall \ k \in [1, p-1], p \mid \binom{p}{k} ∀ k ∈ [ 1 , p − 1 ] , p ∣ ( k p )
( 1 + x ) p = 1 + ( p 1 ) x + ( p 2 ) x 2 + ⋯ ( p p − 1 ) x p − 1 + x p ≡ 1 + x p ( m o d p ) \begin{aligned}
(1+x)^p &= 1 + \binom{p}{1}x + \binom{p}{2} x^2 + \cdots \binom{p}{p-1}x^{p-1} + x^p \\
&\equiv 1 + x^p (\bmod \ p)
\end{aligned}
( 1 + x ) p = 1 + ( 1 p ) x + ( 2 p ) x 2 + ⋯ ( p − 1 p ) x p − 1 + x p ≡ 1 + x p ( m o d p )
又因为 n = ⌊ n p ⌋ ⋅ p + a 0 , a 0 = n m o d p n = \lfloor \frac{n}{p} \rfloor \cdot p + a_0, \quad a_0 = n \bmod p n = ⌊ p n ⌋ ⋅ p + a 0 , a 0 = n m o d p ,定义等式 ( 1 ) (1) ( 1 )
( 1 + x ) n = ( 1 + x ) ⌊ n p ⌋ ⋅ p ⋅ ( 1 + x ) a 0 ≡ ( 1 + x p ) ⌊ n p ⌋ ⋅ ( 1 + x ) a 0 ( m o d p ) ≡ ( ∑ i = 0 ⌊ n / p ⌋ ( ⌊ n / p ⌋ i ) x i ⋅ p ) ⋅ ( ∑ j = 0 a 0 ( a 0 j ) x j ) \begin{aligned}
(1 + x)^n &= (1+x)^{\lfloor \frac{n}{p} \rfloor \cdot p} \cdot (1+x)^{a_0} \\
&\equiv (1 + x^p)^{\lfloor \frac{n}{p} \rfloor} \cdot (1 + x)^{a_0} (\bmod \ p) \\
&\equiv \left( \sum_{i=0}^{\lfloor n/p \rfloor} \binom{\lfloor n/p \rfloor}{i} x^{i \cdot p} \right) \cdot \left( \sum_{j=0}^{a_0} \binom{a_0}{j} x^j \right)
\end{aligned}
( 1 + x ) n = ( 1 + x ) ⌊ p n ⌋ ⋅ p ⋅ ( 1 + x ) a 0 ≡ ( 1 + x p ) ⌊ p n ⌋ ⋅ ( 1 + x ) a 0 ( m o d p ) ≡ ⎝ ⎛ i = 0 ∑ ⌊ n / p ⌋ ( i ⌊ n / p ⌋ ) x i ⋅ p ⎠ ⎞ ⋅ ( j = 0 ∑ a 0 ( j a 0 ) x j )
根据费马小定理,定义函数
f ( x ) ≡ ( 1 + b x m ) p ( m o d p ) ≡ ( 1 + b x m ) ( m o d p ) \begin{aligned}
f(x) &\equiv (1 + bx^m)^p (\bmod \ p) \\
&\equiv (1 + bx^m) (\bmod \ p)
\end{aligned}
f ( x ) ≡ ( 1 + b x m ) p ( m o d p ) ≡ ( 1 + b x m ) ( m o d p )
又根据以上推论可知
f ( x ) ≡ ( 1 + b x m ) p ( m o d p ) ≡ ( 1 + b x p m ) ( m o d p ) ≡ ( 1 + b x p m ) p ( m o d p ) ≡ f ( x p ) \begin{aligned}
f(x) &\equiv (1 + bx^m)^p (\bmod \ p) \\
&\equiv (1 + bx^{pm}) (\bmod \ p) \equiv (1 + bx^{pm})^{p} (\bmod \ p) \\
&\equiv f(x^p)
\end{aligned}
f ( x ) ≡ ( 1 + b x m ) p ( m o d p ) ≡ ( 1 + b x p m ) ( m o d p ) ≡ ( 1 + b x p m ) p ( m o d p ) ≡ f ( x p )
( n m ) n \choose m ( m n ) 即为左边第 m m m 项,x x x 的幂指数为 m m m ,那么 m = i ⋅ p + j m = i \cdot p + j m = i ⋅ p + j
因为 m o d p \bmod \ p m o d p ,对于右边,因为 a 0 = n m o d p ⩽ p − 1 a_0 = n \bmod p \leqslant p-1 a 0 = n m o d p ⩽ p − 1 ,对于 ( a 0 j ) \binom{a_0}{j} ( j a 0 ) 没有 p p p 的倍数项
( 1 + x ) n (1+x)^n ( 1 + x ) n 二项展开,根据上面的结论 f ( x ) ≡ f ( x p ) f(x) \equiv f(x^p) f ( x ) ≡ f ( x p ) ,只有 x x x 的幂指为 p p p 的倍数时,( 1 ) (1) ( 1 ) 才成立( ⌊ n / p ⌋ i ) = ( ⌊ n / p ⌋ ⌊ m / p ⌋ ) , j = m m o d p , a 0 = n m o d p \binom{\lfloor n/p \rfloor}{i} = \binom{\lfloor n/p \rfloor}{\lfloor m/p \rfloor}, \quad j = m \bmod \ p, \ a_0 = n \bmod p
( i ⌊ n / p ⌋ ) = ( ⌊ m / p ⌋ ⌊ n / p ⌋ ) , j = m m o d p , a 0 = n m o d p
生成函数系数相乘,即有( n m ) m o d p = ( ⌊ n / p ⌋ ⌊ m / p ⌋ ) ( n m o d p m m o d p ) m o d p \binom{n}{m} \bmod p = \binom{\lfloor n/p \rfloor}{\lfloor m/p \rfloor} \binom{n \bmod p}{m \bmod p} \bmod p
( m n ) m o d p = ( ⌊ m / p ⌋ ⌊ n / p ⌋ ) ( m m o d p n m o d p ) m o d p
Lucas 定理求组合数
C ( a , b ) = ( a b ) = a ( a − 1 ) ⋯ ( a − b + 1 ) b ! C(a, b) = \binom{a}{b} = \frac{a(a-1) \cdots (a-b+1)}{b!}
C ( a , b ) = ( b a ) = b ! a ( a − 1 ) ⋯ ( a − b + 1 )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 const int mod = 1000007; int ksm(int a, int k) { int res = 1; for (; k; k >>= 1) { if (k & 1) res = (ll)res * a % mod; a = (ll)a * a % mod; } return res; } int C(int a, int b, int mod) { if (a < b) return 0; ll x = 1, y = 1; for (int i = a, j = 1; j <= b; i--, j++) { x = x * i % mod; y = y * j % mod; } return x * (ll)ksm(y, mod-2, mod) % mod; } int lucas(ll a, ll b) { if (a < mod && b < mod) return C(a, b); return (ll)C(a % mod, b % mod) * lucas(a/mod, b/mod) % mod; }
分解质因数求组合数
当我们需要求出组合数的真实值,而非对某个数的余数时,分解质因数比较好用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 const int maxn = 1e5 + 10; int st[maxn]; vector<int> primes, sum; int a, b; void get_primes(int n) { for (int i = 2; i <= n; i++) { if (!st[i]) primes.push_back(i); for (int j = 0; primes[j] <= n/i; j++) { st[primes[j]*i] = true ; if (i % primes[j] == 0) break ; } } } int get(int n, int p) { int res = 0; while (n) res += n/p, n /= p; return res; } vector<int> mul(vector<int> a, int b) { vector<int> c; int t = 0; for (auto x : a) { t += x * b; c.push_back(t % 10); t /= 10; } while (t) c.push_back(t % 10), t /= 10; return c; } void calc () { get_primes(a); sum.resize(primes.size()); for (int i = 0; i < primes.size(); i++) { int p = primes[i]; sum[i] = get(a, p) - get(b, p) - get(a-b, p); } vector<int> res; res.push_back(1); for (int i = 0; i < primes.size(); i++) _for(_, 0, sum[i]) res = mul(res, primes[i]); }
数学基础问题
GCD LCM
G ⋅ L = a ⋅ b G \cdot L = a \cdot b G ⋅ L = a ⋅ b
G > L or G m o d L ≠ 0 G > L \ \textbf{or} \ G \bmod L \neq 0 G > L or G m o d L = 0 ,无解
否则如果 a ⩽ b a \leqslant b a ⩽ b ,那么 a a a 最小一定是 gcd ( a , b ) \text{gcd}(a, b) gcd ( a , b )
Benefit
A ⋅ B = C ⋅ gcd ( A , B ) A \cdot B = C \cdot \text{gcd}(A, B) A ⋅ B = C ⋅ gcd ( A , B )
B = C A ⋅ gcd ( A , B ) = C ′ ⋅ gcd ( A , B ) B = \frac{C}{A} \cdot \text{gcd}(A, B) = C' \cdot \text{gcd}(A, B)
B = A C ⋅ gcd ( A , B ) = C ′ ⋅ gcd ( A , B )
如果 B B B 最小必然有 gcd ( A , B ) = 1 \text{gcd}(A, B) = 1 gcd ( A , B ) = 1 ,否则
B ← B / gcd ( A , B ) B \leftarrow B / \text{gcd}(A, B) B ← B / gcd ( A , B ) 可以得到一个更小的 B B B
let C ′ ← C / A , B ← C ′ \textbf{let} \ C' \leftarrow C/A, \quad B \leftarrow C' let C ′ ← C / A , B ← C ′
d ← gcd ( A , B ) d \leftarrow \textbf{gcd}(A, B) d ← gcd ( A , B )
while d > 1 \textbf{while} \quad d > 1 while d > 1 :
C ′ × = d , gcd ( A , B ) / = d \quad C' \times = d, \quad \text{gcd}(A, B) / = d C ′ × = d , gcd ( A , B ) / = d
gcd ( A , B ) / = d ⇔ A / = d \text{gcd}(A, B) /= d \Leftrightarrow A /= d gcd ( A , B ) / = d ⇔ A / = d
最后令 B = C ′ × ∏ d B = C' \times \prod d B = C ′ × ∏ d
迭代过程如下
B = C ′ ⋅ 1 B = C ′ / d ⋅ ( 1 ⋅ d ) = B / d ⋅ ( 1 ⋅ d ) B = B / d 2 ⋅ ( 1 ⋅ d 2 ) ⋮ B = B / gcd ⋅ ( 1 ⋅ gcd ) \begin{aligned}
B &= C' \cdot 1 \\
B &= C'/d \cdot (1 \cdot d) = B/d \cdot (1 \cdot d) \\
B &= B/d^2 \cdot (1 \cdot d^2) \\
\vdots \\
B &= B/\text{gcd} \cdot (1 \cdot \text{gcd})
\end{aligned}
B B B ⋮ B = C ′ ⋅ 1 = C ′ / d ⋅ ( 1 ⋅ d ) = B / d ⋅ ( 1 ⋅ d ) = B / d 2 ⋅ ( 1 ⋅ d 2 ) = B / gcd ⋅ ( 1 ⋅ gcd )
算法执行过程实际上是倒着 执行上述迭代
How do you add
本例实际上是一个隔板法
( n + k − 1 k − 1 ) \binom{n+k-1}{k-1}
( k − 1 n + k − 1 )
就是本例的答案