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  ) 
就是本例的答案