数学基础
此部分由我前 队友完成。浏览体验可能会有所不同因为水平比我强
有些地方无法正常浏览,可能是因为我队友有些用的LateX语法,markdown渲染不出来
组合数
1. 求组合数
根据不同的数据范围,求组合数也可以运用不同的方法。总的式子:
C a b = a ! b ! ( a − b ) ! C_a^b=\frac{a!}{b!(a-b)!} C a b = b ! ( a − b ) ! a !
表示从a a a 个物品中选出b b b 个的方案数。
(1) 递推法
使用递推式C a b = C a − 1 b + C a − 1 b − 1 C_a^b=C_{a-1}^b+C_{a-1}^{b-1} C a b = C a − 1 b + C a − 1 b − 1
证明:考虑已经得知了C a − 1 k , k ∈ [ 0 , b ] C_{a-1}^k,k\in[0,b] C a − 1 k , k ∈ [ 0 , b ] 的结果,那么当前有a a a 个物品时,第a a a 个物品要么被选,要么不被选中。若被选中,则方案一共有C a − 1 b − 1 C_{a-1}^{b-1} C a − 1 b − 1 个,若不被选中,则方案有C a − 1 b C_{a-1}^b C a − 1 b 个,方案累加,得证。
时间复杂度O ( n 2 ) O(n^2) O ( n 2 )
1 2 3 4 5 6 7 8 9 10 void init () { for (int i=0 ;i<N;++i){ for (int j=0 ;j<=i;++j){ if (!j) C[i][j] = 1 ; else { C[i][j] = (C[i-1 ][j-1 ] + C[i-1 ][j]) % M; } } } }
(2) 通过预处理逆元的方式求组合数
直接求出a ! , 1 b ! , 1 ( a − b ) ! a!,\frac{1}{b!},\frac{1}{(a-b)!} a ! , b ! 1 , ( a − b ) ! 1 ,然后再相乘。
时间复杂度O ( n log n ) O(n\log n) O ( n log n )
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 int n;const int N = 1e5 + 5 ;const ll M = 1e9 + 7 ;ll fac[N], invf[N]; void init () { fac[0 ] = invf[0 ] = 1LL ; for (int i=1 ;i<N;++i){ fac[i] = fac[i-1 ] * i % M; invf[i] = invf[i-1 ] * qmi (i, M-2 , M) % M; } } int main (void ) { scanf ("%d" , &n); init (); ll a, b; for (int i=1 ;i<=n;++i){ scanf ("%lld%lld" , &a, &b); ll ans = fac[a] * invf[b] % M * invf[a - b] % M; printf ("%lld\n" , ans); } return 0 ; }
(3) 分解质因数法求组合数 (高精度)
首先,可以把a ! a! a ! 写成a ! = p 1 a 1 × p 2 a 2 × . . . × p k a k a!=p_1^{a_1}\times p_2^{a_2}\times ...\times p_k^{a_k} a ! = p 1 a 1 × p 2 a 2 × . . . × p k a k 的形式,那么C a b C_a^b C a b 的答案肯定可以写成C a b = p 1 b 1 × p 2 b 2 × . . . × p k b k , ∀ i ∈ [ 1 , k ] , b i ≤ a i C_a^b=p_1^{b_1}\times p_2^{b_2}\times ...\times p_k^{b_k},\forall i\in[1,k],b_i\leq a_i C a b = p 1 b 1 × p 2 b 2 × . . . × p k b k , ∀ i ∈ [ 1 , k ] , b i ≤ a i 。
因此,设s i s_i s i 为 a ! a! a ! 中包含的质因子 p i p_i p i 的个数减去b ! , ( a − b ) ! b!,(a-b)! b ! , ( a − b ) ! 中包含的p i p_i p i 的个数,则C a b = ∏ i = 1 k p i s i C_a^b=\prod_{i=1}^{k} p_i^{s_i} C a b = ∏ i = 1 k p i s i 。
那么,a ! a! a ! 包含的质因子个数为多少呢?答案是⌊ a p ⌋ + ⌊ a p 2 ⌋ + ⌊ a p 3 ⌋ . . . \lfloor\frac{a}{p}\rfloor+\lfloor\frac{a}{p^2}\rfloor+\lfloor\frac{a}{p^3}\rfloor... ⌊ p a ⌋ + ⌊ p 2 a ⌋ + ⌊ p 3 a ⌋ . . . 。
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 48 49 50 51 52 53 54 55 56 57 58 const int N = 5000 + 5 ;int p[N], sa[N], cnt;bool st[N];void Euler (int n) { for (int i=2 ;i<=n;++i){ if (!st[i]) p[++cnt] = i; for (int j=1 ;p[j]<=n/i;++j){ st[p[j] * i] = true ; if (i % p[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 > res; int t = 0 ; for (int i=0 ;i<A.size ();++i){ t += A[i]*b; res.push_back (t % 10 ), t /= 10 ; } while (t){ res.push_back (t%10 ), t /= 10 ;} while (res.back ()==0 && res.size ()>1 ) res.pop_back (); return res; } int main (void ) { int a, b; scanf ("%d%d" , &a, &b); Euler (a); for (int i=1 ;i<=cnt;++i){ int cur = p[i]; sa[i] = get (a, cur) - get (a-b, cur) - get (b, cur); } vector<int > res; res.push_back (1 ); for (int i=1 ;i<=cnt;++i){ for (int j=0 ;j<sa[i];++j){ res = mul (res, p[i]); } } for (int i=res.size ()-1 ;i>=0 ;--i){ printf ("%d" , res[i]); } puts ("" ); return 0 ; }
(4) Lucas定理求组合数
其中,组合数直接通过公式C a b = a ! b ! ( a − b ) ! = a × ( a − 1 ) × . . . × ( a − b + 1 ) b ! C_a^b=\frac{a!}{b!(a-b)!}=\frac{a\times (a-1)\times...\times (a-b+1)}{b!} C a b = b ! ( a − b ) ! a ! = b ! a × ( a − 1 ) × . . . × ( a − b + 1 ) 求得
1 2 3 4 5 6 7 8 9 10 11 12 13 14 ll C (ll a, ll b, ll p) { if (b > a) return 0 ; ll res = 1 ; for (int i=1 , j=a;i<=b;++i, --j){ res = res * j % p; res = res * qmi (i, p-2 , p) % p; } return res; } ll lucas (ll a, ll b, ll p) { if (a<p && b<p) return C (a, b, p); return C (a%p, b%p, p) * lucas (a/p, b/p, p) % p; }
(5) 取对数求组合数(可能有浮点误差)
此种方法大概可以计算1000以内的组合数。
先假设m ≤ n 2 m\leq \frac{n}{2} m ≤ 2 n 吧,否则因为C ( n , m ) = C ( n , m − n ) C(n,m)=C(n,m-n) C ( n , m ) = C ( n , m − n ) ,令 m = m − n m=m-n m = m − n 。
则x = ln C ( n , m ) = ln ( n ! m ! ( n − m ) ! ) = ∑ i = 1 n ln i − ∑ i = 1 m ln i − ∑ i = 1 n − m ln i x=\ln C(n,m)=\ln (\frac{n!}{m!(n-m)!})=\sum\limits _{i=1}^n\ln i-\sum\limits _{i=1}^m\ln i-\sum\limits _{i=1}^{n-m}\ln i x = ln C ( n , m ) = ln ( m ! ( n − m ) ! n ! ) = i = 1 ∑ n ln i − i = 1 ∑ m ln i − i = 1 ∑ n − m ln i
这样就可以直接计算前缀和ln n \ln n ln n ,则C ( n , m ) = e x C(n,m)=e^x C ( n , m ) = e x 。
2. 常用公式
(1) 二项式定理
(x+y)^n=\left(\begin{array}{} n\\ 0 \end{array}\right)x^ny^0+\left(\begin{array}{} n\\ 1 \end{array}\right)x^{n-1}y^1+...+\left(\begin{array}{} n\\ n-1 \end{array}\right)x^1y^{n-1}+\left(\begin{array}{} n\\ n \end{array}\right)x^0y^n
也可写成(x+y)^n=\sum\limits_{k=0}^n\left(\begin{array}{} n\\ k \end{array}\right)x^{n-k}y^k=\sum\limits_{k=0}^n\left(\begin{array}{} n\\ k \end{array}\right)x^{k}y^{n-k}
其中,\left(\begin{array}{} n\\ k \end{array}\right)=C_n^k=\frac{n!}{k!(n-k)!}。
二项式定理的一个常用形式为( 1 + x ) n = ∑ k = 0 n C n k x k (1+x)^n=\sum\limits_{k=0}^n C_n^kx^k ( 1 + x ) n = k = 0 ∑ n C n k x k 。
那么很明显,( 1 − x ) n = ∑ k = 0 n ( − 1 ) k C n k x k (1-x)^n=\sum\limits_{k=0}^n (-1)^kC_n^kx^k ( 1 − x ) n = k = 0 ∑ n ( − 1 ) k C n k x k
(2) 其他的一些小公式
C n 0 + C n 1 + . . . + C n n = 2 n C_n^0+C_n^1+...+C_n^n=2^n C n 0 + C n 1 + . . . + C n n = 2 n (从n n n 取任意个数,显然二进制取或不取)
C a b = C a − 1 b + C a − 1 b − 1 C_a^b=C_{a-1}^b+C_{a-1}^{b-1} C a b = C a − 1 b + C a − 1 b − 1 (类似DP思想,对于第b b b 个数,取或不取)
C m + n n = C m 0 C n n + C m 1 C n n − 1 + . . . + C m n C n 0 C_{m+n}^n=C_m^0C_n^n+C_m^1C_n^{n-1}+...+C_m^nC_n^0 C m + n n = C m 0 C n n + C m 1 C n n − 1 + . . . + C m n C n 0 (从m m m 个数取n n n 个,n n n 个取0个开始遍历)
C n m = C n n − m C_n^m=C_n^{n-m} C n m = C n n − m (显然从n n n 中取m m m 个的方案数和从n n n 中取n − m n-m n − m 个的方案数是一样的)
(这些递推式还是挺容易证明的就不写了,敲公式好累)
(2) 乘法原理和加法原理:类类独立,步步相关
加法原理:做一件事情,完成它可以有n n n 类办法,在第一类办法中有m 1 m_1 m 1 种不同的方法,在第二类办法中有m 2 m_2 m 2 种不同的方法,……,在第n n n 类办法中有m n m_n m n 种不同的方法。那么完成这件事共有N = ∑ i = 1 n m i N=\sum_{i=1}^n m_i N = ∑ i = 1 n m i 种不同的方法。
乘法原理:做一件事情,完成它需要分成n个步骤,做第一步有m 1 m_1 m 1 种不同的方法,做第二步有m2种不同的方法,……,做第n n n 步有种m n m_n m n 不同的方法,那么完成这件事有N = ∏ i = 1 n m i N=\prod_{i=1}^n m_i N = ∏ i = 1 n m i 种不同的方法。
(3) 可重复元素的组合问题
从n个元素中,可重复的挑选m个元素组成集合,求:不同的集合有多少个?
答案:C n + m − 1 m C_{n+m-1}^m C n + m − 1 m
证明:隔板法。
∣∗∣∗∗∗∗∣∣∗∗∗∣∣∗∣(|是边界,*是球)
假设这里有n个盒子,m个球,如果有k个球被放在了第i个盒子里,那么就相当于第i个数被选了k次。那么去掉两端边界,就相当于摆放n-1个盒子的边界和m个球有多少种摆放方式。从里面选择r个位置放球 剩余的都是盒子边界,因此答案就是C n + m − 1 m C_{n+m-1}^m C n + m − 1 m 。
这里有位老哥用dp在O ( n 2 ) O(n^2) O ( n 2 ) 搞定了,俺觉得也可以一看wwww,部分dp表真是一个好东西啊。
https://blog.csdn.net/m0_37602827/article/details/100624871
3. 常用定理
(1) Lucas定理
结论 :C a b ≡ C a m o d p b m o d p × C a / p b / p ( m o d p ) C_a^b \equiv C_{a\mod p}^{b \mod p}\times C_{a/p}^{b/p} (\mod p) C a b ≡ C a m o d p b m o d p × C a / p b / p ( m o d p ) (p p p 为素数)
它还可以写成如下形式:
若p p p 为素数,则n n n 的p p p 进制表示为( a k , a k − 1 , . . . , a 0 ) (a_k,a_{k-1},...,a_0) ( a k , a k − 1 , . . . , a 0 ) ,m m m 的p p p 进制表示为( b k , b k − 1 , . . . , b 0 ) (b_k,b_{k-1},...,b_0) ( b k , b k − 1 , . . . , b 0 )
则C n m ≡ C a k b k ⋅ C a k − 1 b k − 1 . . . C a 0 b 0 ( m o d p ) C_n^m\equiv C_{a_k}^{b_k}·C_{a_{k-1}}^{b_{k-1}}...C_{a_0}^{b_0} (\mod p) C n m ≡ C a k b k ⋅ C a k − 1 b k − 1 . . . C a 0 b 0 ( m o d p )
很容易看出来,它就是形式一运用数学归纳法可以得出的结论。
证明 :
引理1 C ( p , x ) ≡ 0 ( m o d p ) , 0 < x < p C(p,x) \equiv 0(\mod p), 0<x<p C ( p , x ) ≡ 0 ( m o d p ) , 0 < x < p
证明:$C(p,x)\equiv \frac{p!}{x!(p-x)!}\equiv \frac{p\times (p-1)!}{x\times(x-1)\times(p-x)!} $
由于p p p 为素数,所以∀ x ∈ ( 0 , p ) , ( p , x ) = 1 \forall x\in(0,p),(p,x)=1 ∀ x ∈ ( 0 , p ) , ( p , x ) = 1 ,故x ∣ C p − 1 x − 1 x|C_{p-1}^{x-1} x ∣ C p − 1 x − 1 ,则p ∣ ( p x C p − 1 x − 1 ) p|(\frac{p}{x}C_{p-1}^{x-1}) p ∣ ( x p C p − 1 x − 1 ) 。
所以C ( p , x ) ≡ p × ( x − 1 m o d p ) × C ( p − 1 , x − 1 ) ≡ 0 m o d p C(p,x) \equiv p\times(x^{-1} \mod p)\times C(p-1,x-1)\equiv 0\mod p C ( p , x ) ≡ p × ( x − 1 m o d p ) × C ( p − 1 , x − 1 ) ≡ 0 m o d p
引理2 ( 1 + x ) p ≡ ( 1 + x p ) m o d p (1+x)^p \equiv (1+x^p) \mod p ( 1 + x ) p ≡ ( 1 + x p ) m o d p
根据二项式定理可得( 1 + x ) p = ∑ k = 0 p C p k x k (1+x)^p=\sum\limits _{k=0}^pC_p^kx^k ( 1 + x ) p = k = 0 ∑ p C p k x k
由引理1可得∑ k = 0 p C p k x k ≡ C ( p , 0 ) x 0 + C ( p , p ) x p ≡ ( 1 + x p ) m o d p \sum\limits _{k=0}^pC_p^kx^k\equiv C(p,0)x^0+C(p,p)x^p\equiv (1+x^p) \mod p k = 0 ∑ p C p k x k ≡ C ( p , 0 ) x 0 + C ( p , p ) x p ≡ ( 1 + x p ) m o d p
下面正式推导Lucas定理。
假设n = q 1 p + r 1 , m = q 2 p + r 2 n=q_1p+r_1,m=q_2p+r_2 n = q 1 p + r 1 , m = q 2 p + r 2 ,则q 1 = n / p , q 2 = m / p q_1=n/p,q_2=m/p q 1 = n / p , q 2 = m / p
( 1 + x ) n ≡ ( 1 + x ) q 1 p + r 1 ≡ ( 1 + x ) q 1 p × ( 1 + x ) r 1 ≡ [ ( 1 + x ) p ] q 1 × ( 1 + x ) r 1 ( 1 + x p ) q 1 × ( 1 + x ) r 1 ≡ ∑ i = 0 q 1 C ( q 1 , i ) x p × i × ∑ j = 0 r 1 C ( r 1 , j ) x j ( m o d p ) (1+x)^n\equiv (1+x)^{q_1p+r_1}\equiv (1+x)^{q_1p}\times(1+x)^{r_1}\equiv [(1+x)^p]^{q_1}\times(1+x)^{r_1} \\ (1+x^p)^{q_1}\times(1+x)^{r_1} \equiv \sum\limits _{i=0}^{q_1}C(q_1,i)x^{p\times i}\times \sum\limits _{j=0}^{r_1}C(r_1,j)x^{j} (\mod p) ( 1 + x ) n ≡ ( 1 + x ) q 1 p + r 1 ≡ ( 1 + x ) q 1 p × ( 1 + x ) r 1 ≡ [ ( 1 + x ) p ] q 1 × ( 1 + x ) r 1 ( 1 + x p ) q 1 × ( 1 + x ) r 1 ≡ i = 0 ∑ q 1 C ( q 1 , i ) x p × i × j = 0 ∑ r 1 C ( r 1 , j ) x j ( m o d p )
又( 1 + x ) n = ∑ i = 0 n C ( n , i ) x i (1+x)^n=\sum\limits _{i=0}^{n}C(n,i)x^{i} ( 1 + x ) n = i = 0 ∑ n C ( n , i ) x i
∴ ∑ i = 0 n C ( n , i ) x i ≡ ∑ i = 0 q 1 C ( q 1 , i ) x p × i × ∑ j = 0 r 1 C ( r 1 , j ) x j ( m o d p ) \therefore \sum\limits _{i=0}^{n}C(n,i)x^{i}\equiv \sum\limits _{i=0}^{q_1}C(q_1,i)x^{p\times i}\times \sum\limits _{j=0}^{r_1}C(r_1,j)x^{j} (\mod p) ∴ i = 0 ∑ n C ( n , i ) x i ≡ i = 0 ∑ q 1 C ( q 1 , i ) x p × i × j = 0 ∑ r 1 C ( r 1 , j ) x j ( m o d p )
由二项式定理可知,C n m C_n^m C n m 为( 1 + x ) n (1+x)^n ( 1 + x ) n 展开式中x m x^m x m 项前面的系数
∵ m = q 2 p + r 2 , ∴ q 2 = m / p , r 2 = m − ⌊ m p ⌋ × p \because m=q_2p+r_2,\therefore q_2=m/p,r_2=m-\lfloor \frac{m}{p} \rfloor \times p ∵ m = q 2 p + r 2 , ∴ q 2 = m / p , r 2 = m − ⌊ p m ⌋ × p
C ( n , m ) x m ≡ C ( q 1 , q 2 ) x p × q 2 × C ( r 1 , r 2 ) x r 2 ( m o d p ) C(n,m)x^{m}\equiv C(q_1,q_2)x^{p\times q_2}\times C(r_1,r_2)x^{r_2} (\mod p) C ( n , m ) x m ≡ C ( q 1 , q 2 ) x p × q 2 × C ( r 1 , r 2 ) x r 2 ( m o d p )
即C n m ≡ C q 1 q 2 ⋅ C r 1 r 2 ( m o d p ) C_n^m\equiv C_{q_1}^{q_2}·C_{r_1}^{r_2} (\mod p) C n m ≡ C q 1 q 2 ⋅ C r 1 r 2 ( m o d p ) ,得证。
推论1 C ( n , m ) C(n,m) C ( n , m ) 为奇数的充要条件为,在二进制表示下(p = 2 p=2 p = 2 ),∀ i ∈ [ 0 , k ] , a i ≥ b k \forall i\in[0,k],a_i\geq b_k ∀ i ∈ [ 0 , k ] , a i ≥ b k 。
证明:首先,C ( 0 , 0 ) = 1 , C ( 0 , 1 ) = 0 , C ( 1 , 0 ) = 1 , C ( 1 , 1 ) = 1 C(0,0)=1,C(0,1)=0,C(1,0)=1,C(1,1)=1 C ( 0 , 0 ) = 1 , C ( 0 , 1 ) = 0 , C ( 1 , 0 ) = 1 , C ( 1 , 1 ) = 1 。
考虑C ( n , m ) m o d 2 C(n,m)\mod 2 C ( n , m ) m o d 2 ,则对于n n n 上的为1的位a i a_i a i ,b i b_i b i 能取0或1,对于a i = 0 a_i=0 a i = 0 ,则b i b_i b i 必须为0,得证。
例题:HDU4349 Xiao Ming’s Hope 结论就是2 s u m ( a i = 1 ) 2^{sum(a_i=1)} 2 s u m ( a i = 1 ) 这玩意还真会考到啊orz
(2) 扩展Lucas定理
前置知识:卢卡斯定理 中国剩余定理
扩展Lucas定理用于求解以下公式:
C a b m o d p C_a^b \mod p C a b m o d p ,其中p p p 不一定是质数。
由整数的唯一分解定理,对p p p 进行质因数分解,p = p 1 a 1 × p 2 a 2 × . . × p k a k p=p_1^{a_1}\times p_2^{a_2}\times .. \times p_k^{a_k} p = p 1 a 1 × p 2 a 2 × . . × p k a k ,显然∀ i , j ∈ [ 1 , k ] , i ≠ j , ( p i a i , p j a j ) = 1 \forall i,j \in [1,k], i\neq j, (p_i^{a_i},p_j^{a_j})=1 ∀ i , j ∈ [ 1 , k ] , i = j , ( p i a i , p j a j ) = 1 。
提高篇中已证明
筛法
1. 埃氏筛
1 2 3 4 5 6 7 8 9 10 11 12 13 14 void Eratosthenes () { for (int i=2 ;i<=n;++i) st[i] = 1 ; for (int i=2 ;i<=n;++i){ if (st[i]){ p[++cnt] = i; if ((ll)i*i > n) continue ; for (int j=i*i;j<=n;j+=i){ st[j] = false ; } } } }
由于调和级数1 + 1 2 + 1 3 + . . . + 1 N = l n ( N + 1 ) + r 1+\frac{1}{2}+\frac{1}{3}+...+\frac{1}{N}=ln(N+1)+r 1 + 2 1 + 3 1 + . . . + N 1 = l n ( N + 1 ) + r ,r r r 为欧拉常数,r ≈ 0.5772156649 r\approx 0.5772156649 r ≈ 0 . 5 7 7 2 1 5 6 6 4 9 。因此,时间复杂度可近似为O ( N l n N ) < O ( N l o g N ) O(NlnN)<O(NlogN) O ( N l n N ) < O ( N l o g N ) 。
当只筛质数时,由质数分布定理可得N N N 中约有π ( N ) = N l n N \pi(N)=\frac{N}{ln N} π ( N ) = l n N N 个质数,因此时间复杂度估算结果为O ( N l n N l n N ) O(\frac{NlnN}{lnN}) O ( l n N N l n N ) ,即非常接近O ( N ) O(N) O ( N ) 。在实际计算下,埃氏筛的时间复杂度为O ( N l o g l o g N ) O(NloglogN) O ( N l o g l o g N ) 。
2. 欧拉筛
1 2 3 4 5 6 7 8 9 10 11 12 13 14 void Euler () { for (int i=2 ;i<=n;++i){ if (!st[i]) p[++cnt] = i; for (int j=1 ;p[j]<=n/i;++j){ st[p[j] * i] = true ; if (i % p[j] == 0 ) break ; } } }
质数
定义:大于1,且除了1和它本身之外不再有其他因数的自然数。
质数分布定理:1 1 1 ~n n n 间的质数大概有π ( n ) = n ln n \pi(n)=\frac{n}{\ln n} π ( n ) = ln n n 个
互质数:两个或多个整数的公因数只有1的非0自然数。
约数
1. 试除法求约数
(1) 求所有的质因数
1 2 3 4 5 6 7 8 9 10 11 void div (int x) { for (int i=2 ;(ll)i*i<=x;++i){ int cnt = 0 ; while (x%i==0 ){ x /= i; cnt++; } if (cnt) printf ("%d %d\n" , i, cnt); } if (x > 1 ) printf ("%d %d" , x, 1 ); puts ("" ); }
(2) 求所有的约数
使用筛法先筛质数,再通过dfs枚举约数
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 int n, cnt, tot, dn;const int N = 5e4 + 5 ;bool st[N];int p[N], d[N];pii f[N]; void dfs (int u, int x) { if (u > tot){ d[++dn] = x; return ; } for (int i=0 ;i<=f[u].yy;++i){ dfs (u + 1 , x); x *= f[u].xx; } } void solve () { int a0, a1, b0, b1; scanf ("%d%d%d%d" , &a0, &a1, &b0, &b1); int n = b1; tot = 0 ; for (int i=1 ;p[i]<=n/p[i];++i){ if (n % p[i] != 0 ) continue ; int sm = 0 ; while (n % p[i] == 0 ) n /= p[i], ++sm; f[++tot] = pii (p[i], sm); } if (n > 1 ) f[++tot] = pii (n, 1 ); dn = 0 ; dfs (1 , 1 ); }
2. 约数个数
任何一个正整数n n n 都可以表达为如下形式:
n = p 1 a 1 × p 2 a 2 × . . . × p k a k n=p_1^{a_1}\times p_2^{a_2}\times ...\times p_k^{a_k} n = p 1 a 1 × p 2 a 2 × . . . × p k a k ,其中,∀ i ∈ [ 1 , k ] , p i \forall i\in [1,k], p_i ∀ i ∈ [ 1 , k ] , p i 为质数
那么,对于n n n 的任意约数α \alpha α ,都可以将α \alpha α 写成如下形式:
α = p 1 b 1 × p 2 b 2 × . . . × p k b k \alpha=p_1^{b_1}\times p_2^{b_2}\times ...\times p_k^{b_k} α = p 1 b 1 × p 2 b 2 × . . . × p k b k ,其中,∀ i ∈ [ 1 , k ] \forall i\in [1,k] ∀ i ∈ [ 1 , k ] ,0 ≤ b i ≤ a k 0\leq b_i \leq a_k 0 ≤ b i ≤ a k
那么对于每一个b i b_i b i ,都有( a i + 1 ) (a_i+1) ( a i + 1 ) 种取法,因此,n n n 的所有约数之和为( a 1 + 1 ) ( a 2 + 1 ) . . . ( a k + 1 ) (a_1+1)(a_2+1)...(a_k+1) ( a 1 + 1 ) ( a 2 + 1 ) . . . ( a k + 1 )
小性质:所有int范围内的数,约数之和最多的约为1500
小例子:求n!的约数之和
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 int n, cnt;const int N = 1e6 + 5 ;int p[N], num[N];bool st[N];void Euler (int n) { for (int i=2 ;i<=n;++i){ if (!st[i]) p[++cnt] = i; for (int j=1 ;p[j]<=n/i;++j){ st[p[j] * i] = true ; if (i % p[j] == 0 ) break ; } } } int main (void ) { scanf ("%d" , &n); Euler (N - 1 ); for (int i=2 ;i<=n;++i){ if (st[i]) continue ; for (ll j = i;j<=n;j*=i){ num[i] += n / j; } } for (int i=2 ;i<=n;++i){ if (num[i]){ printf ("%d %d\n" , i, num[i]); } } return 0 ; }
3. 约数之和
∑ α i = ( p 1 0 + p 1 1 + . . . + p 1 a 1 ) ( p 2 0 + p 2 1 + . . . + p 2 a 2 ) . . . ( p k 0 + p k 1 + . . . + p k a k ) \sum \alpha_i=(p_1^0+ p_1^1+ ...+ p_1^{a_1})(p_2^0+ p_2^1+ ...+ p_2^{a_2})...(p_k^0+ p_k^1+ ...+ p_k^{a_k}) ∑ α i = ( p 1 0 + p 1 1 + . . . + p 1 a 1 ) ( p 2 0 + p 2 1 + . . . + p 2 a 2 ) . . . ( p k 0 + p k 1 + . . . + p k a k )
展开该式子,就可以得到α = p 1 b 1 × p 2 b 2 × . . . × p k b k \alpha=p_1^{b_1}\times p_2^{b_2}\times ...\times p_k^{b_k} α = p 1 b 1 × p 2 b 2 × . . . × p k b k ,所有的b i b_i b i 取值的组合相加。实际上,这个式子展开一共有( a 1 + 1 ) ( a 2 + 1 ) . . ( a k + 1 ) (a_1+1)(a_2+1)..(a_k+1) ( a 1 + 1 ) ( a 2 + 1 ) . . ( a k + 1 ) 项,也对应了约数个数。
4. 欧几里得算法
欧几里得算法基于的性质:
若d ∣ a , a ∣ b d|a, a|b d ∣ a , a ∣ b ,则d ∣ ( a x + b y ) d|(ax+by) d ∣ ( a x + b y )
( a , b ) = ( b , a m o d b ) (a,b)=(b,a~mod~b) ( a , b ) = ( b , a m o d b )
第二条性质证明:
∵ a m o d b = a − ⌊ a b ⌋ × b \because a~mod~b=a-\lfloor \frac{a}{b} \rfloor\times b ∵ a m o d b = a − ⌊ b a ⌋ × b ,令c = ⌊ a b ⌋ c=\lfloor \frac{a}{b} \rfloor c = ⌊ b a ⌋
则问题等价于证明( a , b ) = ( b , a − c × b ) (a,b)=(b,a-c\times b) ( a , b ) = ( b , a − c × b )
这个证明方法就和裴蜀定理的证明差不多。
证明:令d = g c d ( a , b ) d=gcd(a,b) d = g c d ( a , b ) ,则d ∣ a , d ∣ b d|a,d|b d ∣ a , d ∣ b ,易得d ∣ ( a − c × b ) d|(a-c\times b) d ∣ ( a − c × b ) 。则d d d 为b , ( a − c × b ) b,(a-c\times b) b , ( a − c × b ) 的公因数。
那么令D = ( b , a − c × b ) D=(b,a-c\times b) D = ( b , a − c × b ) ,d ≤ D d\leq D d ≤ D 。
D ∣ b , D ∣ ( a − c × b ) D|b,D|(a-c\times b) D ∣ b , D ∣ ( a − c × b ) ,易得D ∣ a D|a D ∣ a ,则D ≤ ( a , b ) = d D\leq (a,b)=d D ≤ ( a , b ) = d 。
因此d = D d=D d = D ,即d = g c d ( b , a − c × b ) d=gcd(b,a-c\times b) d = g c d ( b , a − c × b )
欧几里得算法模板
1 2 3 int gcd (int a, int b) { return b ? gcd (b, a % b) : a; }
欧拉函数
1. 欧拉函数的定义
定义φ ( N ) \varphi(N) φ ( N ) 为1 1 1 ~N N N 中与N N N 互质的数,假设N N N 可以表达为p 1 a 1 × p 2 a 2 . . . p k a k p_1^{a_1}\times p_2^{a_2}...p_k^{a_k} p 1 a 1 × p 2 a 2 . . . p k a k ,∀ i ∈ [ 1 , k ] , p i \forall i\in [1,k], p_i ∀ i ∈ [ 1 , k ] , p i 为质数,a i > 0 a_i>0 a i > 0
则φ ( N ) = N × ( 1 − 1 p 1 ) × ( 1 − 1 p 2 ) . . . ( 1 − 1 p k ) \varphi(N)=N\times (1-\frac{1}{p_1})\times(1-\frac{1}{p_2})...(1-\frac{1}{p_k}) φ ( N ) = N × ( 1 − p 1 1 ) × ( 1 − p 2 1 ) . . . ( 1 − p k 1 )
特别的,规定φ ( 1 ) = 1 \varphi(1)=1 φ ( 1 ) = 1 。(唯一和1互质的数就是1本身)
证明:容斥原理
先去掉1~N中p 1 p_1 p 1 的倍数,再去掉1~N中p 2 p_2 p 2 的倍数,……直到去掉1~N中p k p_k p k 的倍数。那么,此时答案就是
N − N p 1 − N p 2 − . . . − N p k N-\frac{N}{p_1}-\frac{N}{p_2}-...-\frac{N}{p_k}
N − p 1 N − p 2 N − . . . − p k N
这个答案,将p i × p j , ( i , j ∈ [ 1 , k ] , i ≠ j ) p_i\times p_j, (i,j\in[1,k], i\neq j) p i × p j , ( i , j ∈ [ 1 , k ] , i = j ) 重复减去了多次,需要把它们加回来
N − N p 1 − N p 2 − . . . − N p k + N p 1 p 2 + N p 1 p 3 + . . . + N p k − 1 p k N-\frac{N}{p_1}-\frac{N}{p_2}-...-\frac{N}{p_k}+\frac{N}{p_1p_2}+\frac{N}{p_1p_3}+...+\frac{N}{p_{k-1}p_k}
N − p 1 N − p 2 N − . . . − p k N + p 1 p 2 N + p 1 p 3 N + . . . + p k − 1 p k N
考虑这样的三元组p a p b p c p_ap_bp_c p a p b p c ,它们先是被1中的式子p a , p b , p c p_a,p_b,p_c p a , p b , p c 减去了三次,又在2中的式子p a p b , p a p c , p b p c p_ap_b,p_ap_c,p_bp_c p a p b , p a p c , p b p c 中加了三次,总体没加也没减。因此要把三元组的倍数从N N N 中减去
N − N p 1 − N p 2 − . . . − N p k + N p 1 p 2 + N p 1 p 3 + . . . + N p k − 1 p k − N p 1 p 2 p 3 − . . . − N p k − 2 p k − 1 p k N-\frac{N}{p_1}-\frac{N}{p_2}-...-\frac{N}{p_k}+\frac{N}{p_1p_2}+\frac{N}{p_1p_3}+...+\frac{N}{p_{k-1}p_k}-\frac{N}{p_1p_2p_3}-...-\frac{N}{p_{k-2}p_{k-1}p_{k}}
N − p 1 N − p 2 N − . . . − p k N + p 1 p 2 N + p 1 p 3 N + . . . + p k − 1 p k N − p 1 p 2 p 3 N − . . . − p k − 2 p k − 1 p k N
以此类推,最终化简上面的式子,可得
N × ( 1 − 1 p 1 − . . . − 1 p k + 1 p 1 p 2 + . . . + 1 p k − 1 p k − . . . ) = N × ( 1 − 1 p 1 ) × ( 1 − 1 p 2 ) . . . ( 1 − 1 p k ) N\times(1-\frac{1}{p_1}-...-\frac{1}{p_k}+\frac{1}{p_1p_2}+...+\frac{1}{p_{k-1}p_k}-...)\\=N\times (1-\frac{1}{p_1})\times(1-\frac{1}{p_2})...(1-\frac{1}{p_k})
N × ( 1 − p 1 1 − . . . − p k 1 + p 1 p 2 1 + . . . + p k − 1 p k 1 − . . . ) = N × ( 1 − p 1 1 ) × ( 1 − p 2 1 ) . . . ( 1 − p k 1 )
时间复杂度:欧拉函数时间复杂度最大的地方在于分解质因数,因此时间复杂度为O ( N ) O(\sqrt N) O ( N ) 。
欧拉函数模板
1 2 3 4 5 6 7 8 9 10 11 int phi (int x) { int res = x; for (int i=2 ;(ll)i*i<=x;++i){ if (x % i == 0 ){ res = res / i * (i - 1 ); while (x % i == 0 ) x /= i; } } if (x > 1 ) res = res / x * (x - 1 ); return res; }
2. 欧拉函数的性质
性质1
∑ a = n × φ ( n ) 2 , n > 1 , a ∈ [ 1 , n ) , ( a , n ) = 1 \sum a=\frac{n\times \varphi(n)}{2}, n>1, a\in[1,n),(a,n)=1 ∑ a = 2 n × φ ( n ) , n > 1 , a ∈ [ 1 , n ) , ( a , n ) = 1 。
性质2
∀ n > 2 , φ ( n ) \forall n>2,\varphi(n) ∀ n > 2 , φ ( n ) 为偶数。
证明:∵ ( n , x ) = ( n , n − x ) , n > x \because (n,x)=(n,n-x), n>x ∵ ( n , x ) = ( n , n − x ) , n > x
假设∃ x ∈ [ 1 , n ) \exist x\in[1,n) ∃ x ∈ [ 1 , n ) ,( n , x ) = ( n , n − x ) = 1 , x = n − x , n > 2 (n,x)=(n,n-x)=1,x=n-x, n>2 ( n , x ) = ( n , n − x ) = 1 , x = n − x , n > 2 ,则n = 2 x n=2x n = 2 x ,矛盾。
∴ [ 1 , n ) \therefore[1,n) ∴ [ 1 , n ) 中与n n n 互质的数均是成对出现的,因此[ 1 , n ) [1,n) [ 1 , n ) 与n n n 互质的数是偶数,即φ ( n ) \varphi(n) φ ( n ) 为偶数。
观察( n , x ) = ( n , n − x ) = 1 (n,x)=(n,n-x)=1 ( n , x ) = ( n , n − x ) = 1 ,也说明了任意一对与n n n 互质的数,相加均为n n n ,所以有∑ a = n × φ ( n ) 2 \sum a = \frac{n\times\varphi(n)}{2} ∑ a = 2 n × φ ( n )
性质3
∑ d ∣ n φ ( d ) = n \sum\limits _{d|n} \varphi(d)=n d ∣ n ∑ φ ( d ) = n
设f ( n ) = φ ∗ I = ∑ d ∣ n φ ( d ) f(n)=\varphi * I=\sum\limits _{d|n}\varphi(d) f ( n ) = φ ∗ I = d ∣ n ∑ φ ( d ) ,则f ( n ) f(n) f ( n ) 为一个积性函数。
将n n n 唯一分解为n = ∏ i = 1 k p i a i = p 1 a 1 p 2 a 2 . . p k a k n= \prod_{i=1}^kp_i^{a_i} = p_1^{a_1}p_2^{a_2}..p_k^{a_k} n = ∏ i = 1 k p i a i = p 1 a 1 p 2 a 2 . . p k a k ,则
f ( p i a i ) = φ ( 1 ) + φ ( p i ) + . . + φ ( p i a k ) = 1 + ( p − 1 ) + . . + ( p k − p k − 1 ) = p k f(p_i^{a_i})=\varphi(1)+\varphi(p_i)+..+\varphi(p_i^{a_k})\\=1+(p-1)+..+(p^k-p^{k-1})=p^k f ( p i a i ) = φ ( 1 ) + φ ( p i ) + . . + φ ( p i a k ) = 1 + ( p − 1 ) + . . + ( p k − p k − 1 ) = p k
f ( n ) = f ( p 1 a 1 ) × f ( p 2 a 2 ) × . . × f ( p k a k ) = ∏ i = 1 k p i a i = n f(n)=f(p_1^{a_1})\times f(p_2^{a_2})\times ..\times f(p_k^{a_k})=\prod_{i=1}^kp_i^{a_i}=n f ( n ) = f ( p 1 a 1 ) × f ( p 2 a 2 ) × . . × f ( p k a k ) = ∏ i = 1 k p i a i = n ,证毕。
性质4
欧拉函数为积性函数,但不是完全积性函数,当( n , m ) = 1 (n,m)=1 ( n , m ) = 1 时,满足φ ( m × n ) = φ ( m ) × φ ( n ) \varphi(m\times n)=\varphi(m)\times \varphi(n) φ ( m × n ) = φ ( m ) × φ ( n ) 。那么显然,当n n n 唯一分解后,φ ( n ) = ∏ i = 1 k φ ( p i a i ) \varphi(n)=\prod_{i=1}^k\varphi(p_i^{a_i}) φ ( n ) = ∏ i = 1 k φ ( p i a i ) 。
证明:若( n , m ) = 1 (n,m)=1 ( n , m ) = 1 ,则n , m n,m n , m 没有相同的质因子,记n n n 的质因子个数为c 1 c_1 c 1 ,m m m 的质因子个数为c 2 c_2 c 2 ,则
φ ( n ) × φ ( m ) = n × m × ∏ i = 1 c 1 ( 1 − 1 p i ) × ∏ i = 1 c 2 ( 1 − 1 p i ) = ∏ i = 1 c 1 + c 2 ( 1 − 1 p i ) = φ ( n × m ) \varphi(n)\times \varphi(m)=n\times m\times \prod_{i=1}^{c_1}(1-\frac 1 {p_i})\times \prod_{i=1}^{c_2}(1-\frac 1 {p_i})=\prod_{i=1}^{c_1+c2}(1-\frac 1 {p_i})=\varphi(n\times m) φ ( n ) × φ ( m ) = n × m × ∏ i = 1 c 1 ( 1 − p i 1 ) × ∏ i = 1 c 2 ( 1 − p i 1 ) = ∏ i = 1 c 1 + c 2 ( 1 − p i 1 ) = φ ( n × m )
例题:cf776E The Holmes Children
性质5
φ ( n ) n = ∑ d ∣ n μ ( d ) d \frac {\varphi(n)} n=\sum\limits_{d\mid n}\frac{\mu(d)}{d} n φ ( n ) = d ∣ n ∑ d μ ( d ) 。
写成狄利克雷卷积的形式
φ ∗ I = i d \varphi * I=id φ ∗ I = i d ,φ ∗ I ∗ μ = i d ∗ μ \varphi * I * \mu=id * \mu φ ∗ I ∗ μ = i d ∗ μ
φ ∗ ( I ∗ μ ) = i d ∗ μ \varphi * (I * \mu)=id * \mu φ ∗ ( I ∗ μ ) = i d ∗ μ
φ ∗ e = i d ∗ μ \varphi*e=id*\mu φ ∗ e = i d ∗ μ
即φ ( n ) = ∑ d ∣ n n d × μ ( d ) \varphi(n)=\sum\limits _{d|n}\frac n d\times \mu(d) φ ( n ) = d ∣ n ∑ d n × μ ( d ) ,也就是φ ( n ) n = ∑ d ∣ n μ ( d ) d \frac {\varphi(n)} n=\sum\limits_{d\mid n}\frac{\mu(d)}{d} n φ ( n ) = d ∣ n ∑ d μ ( d )
性质6
当n = p k n=p^k n = p k 时,φ ( n ) = p k − p k − 1 \varphi(n)=p^k-p^{k-1} φ ( n ) = p k − p k − 1
证明:当n n n 只有一个质因数时,φ ( n ) = p k × ( 1 − 1 p ) = p k − p k − 1 \varphi(n)=p^k\times(1-\frac 1 p)=p^k-p^{k-1} φ ( n ) = p k × ( 1 − p 1 ) = p k − p k − 1
3. 筛法求欧拉函数
假设目前已知φ ( i ) \varphi(i) φ ( i ) 的值,p j p_j p j 为某一质数,求φ ( i × p j ) \varphi(i\times p_j) φ ( i × p j ) 的值。
将i i i 表示为p 1 a 1 × p 2 a 2 . . . p k a k p_1^{a_1}\times p_2^{a_2}...p_k^{a_k} p 1 a 1 × p 2 a 2 . . . p k a k ,φ ( i ) = i × ( 1 − 1 p 1 ) × ( 1 − 1 p 2 ) . . . ( 1 − 1 p k ) \varphi(i)=i\times (1-\frac{1}{p_1})\times(1-\frac{1}{p_2})...(1-\frac{1}{p_k}) φ ( i ) = i × ( 1 − p 1 1 ) × ( 1 − p 2 1 ) . . . ( 1 − p k 1 ) ,
i % p j = = 0 i\%p_j==0 i % p j = = 0 时,i × p j i\times p_j i × p j 中不存在新的质因子
∴ φ ( i × p j ) = i × p j × ( 1 − 1 p 1 ) × ( 1 − 1 p 2 ) . . . ( 1 − 1 p k ) = φ ( i ) × p j \therefore \varphi(i\times p_j)=i\times p_j\times (1-\frac{1}{p_1})\times(1-\frac{1}{p_2})...(1-\frac{1}{p_k})=\varphi(i)\times p_j ∴ φ ( i × p j ) = i × p j × ( 1 − p 1 1 ) × ( 1 − p 2 1 ) . . . ( 1 − p k 1 ) = φ ( i ) × p j
i % p j ≠ 0 i\%p_j\neq 0 i % p j = 0 时,p j p_j p j 为新的质因子
\begin{align} & \therefore \varphi(i\times p_j)=i\times p_j\times (1-\frac{1}{p_1})\times(1-\frac{1}{p_2})...(1-\frac{1}{p_k})\times (1-\frac{1}{p_j})\\ & =\varphi(i)\times p_j\times(1-\frac{1}{p_j})\\ & =\varphi(i)\times(p_j-1) \end{align}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 int n, cnt;const int N = 1e6 + 10 ;int E[N], p[N];bool st[N];void Eulers () { E[1 ] = 1 ; for (int i=2 ;i<=n;++i){ if (!st[i]){ p[++cnt] = i; E[i] = i - 1 ; } for (int j=1 ;p[j]<=n/i;++j){ int t = p[j] * i; st[t] = true ; if (i % p[j] == 0 ){ E[t] = E[i] * p[j]; break ; } E[t] = E[i] * (p[j] - 1 ); } } }
快速幂
1. 定义
1 2 3 4 5 6 7 8 9 10 ll qmi (ll a, ll b, ll p) { ll res = 1LL % p; while (b){ if (b & 1 ) res = res * a % p; a = a * a % p; b >>= 1 ; } return res; }
2. 快速幂求逆元
乘法逆元的定义
若对于整数b , m b,m b , m ,有( b , m ) = 1 (b,m)=1 ( b , m ) = 1 (即b , m b,m b , m 互质),并且对于任意的整数a a a ,如果满足b ∣ a b|a b ∣ a ,则∃ x ∈ N \exist x\in N ∃ x ∈ N ,s . t . a b = a × x ( m o d m ) s.t.~~\frac{a}{b}=a\times x(mod~ m) s . t . b a = a × x ( m o d m ) ,则称x x x 为b b b 的模m m m 乘法逆元,记为b − 1 ( m o d m ) b^{-1}(mod~m) b − 1 ( m o d m ) 。
b b b 存在乘法逆元的充要条件是( b , m ) = 1 (b,m)=1 ( b , m ) = 1 ,当m m m 为质数时,b b b 的乘法逆元为b m − 2 b^{m-2} b m − 2 。
小性质
a b ≡ a × x ( m o d m ) \frac{a}{b} \equiv a\times x(mod ~m) b a ≡ a × x ( m o d m ) a b ≡ a × b − 1 ( m o d m ) \frac{a}{b} \equiv a\times b^{-1}(mod ~m) b a ≡ a × b − 1 ( m o d m )
则a ≡ a × b × b − 1 ( m o d m ) a\equiv a\times b \times b^{-1}(mod ~m) a ≡ a × b × b − 1 ( m o d m ) 即 b × b − 1 ≡ 1 ( m o d m ) b\times b^{-1} \equiv 1(mod ~m) b × b − 1 ≡ 1 ( m o d m )
费马小定理
∀ p \forall p ∀ p 为质数,a a a 为任意整数,有$ a^{p-1}\equiv 1(mod ~p)$
小推论:b × b p − 2 ≡ 1 ( m o d p ) b\times b^{p-2}\equiv 1(mod~p) b × b p − 2 ≡ 1 ( m o d p ) ,因此b b b 的乘法逆元为b p − 2 b^{p-2} b p − 2
扩展欧几里得算法
1. 前置知识-裴蜀定理
裴蜀定理 :∀ a , b ∈ Z \forall a,b\in \Z ∀ a , b ∈ Z , 令d = ( a , b ) d=(a,b) d = ( a , b ) ,那么对于任意的整数x , y ∈ Z x,y\in \Z x , y ∈ Z ,a x + b y = k d ax+by=kd a x + b y = k d 。特别的,一定∃ x , y \exist x,y ∃ x , y ,使得a x + b y = d ax+by=d a x + b y = d 成立。
丢番图方程a x + b y = m ax+by=m a x + b y = m 有解,当且仅当m m m 是d d d 的倍数。丢番图方程有解时必然有无穷多个解,每组解x , y x,y x , y 都称为裴蜀数,可用辗转相除法求得。
证明:
(前半句)∵ d ∣ a , d ∣ b \because d|a,d|b ∵ d ∣ a , d ∣ b ,∴ ∀ x , y ∈ Z , d ∣ ( a x + b y ) \therefore \forall x,y\in Z, d|(ax+by) ∴ ∀ x , y ∈ Z , d ∣ ( a x + b y )
(特别的…)设s s s 为a x + b y ax+by a x + b y 的最小正值,令 q = ⌊ a s ⌋ q=\lfloor \frac{a}{s} \rfloor q = ⌊ s a ⌋ ,r = a m o d s r=a ~mod ~s r = a m o d s 。
则r = a − ⌊ a s ⌋ × s = a − q × ( a x + b y ) = a ( 1 − q x ) + b ( − q y ) r=a-\lfloor \frac{a}{s} \rfloor\times s=a-q\times(ax+by)=a(1-qx)+b(-qy) r = a − ⌊ s a ⌋ × s = a − q × ( a x + b y ) = a ( 1 − q x ) + b ( − q y ) ,即r r r 也为a , b a,b a , b 的线性组合
∵ r = a m o d s \because r=a~mod~s ∵ r = a m o d s ,∴ 0 ≤ r < s \therefore 0\leq r <s ∴ 0 ≤ r < s
又s s s 为a x + b y ax+by a x + b y 的最小正值,可得r = 0 r=0 r = 0 ,即a m o d s = 0 a ~mod~s=0 a m o d s = 0 。
∴ s ∣ a \therefore s|a ∴ s ∣ a ,再设r 2 = b m o d s r_2=b~mod~s r 2 = b m o d s ,同理可得s ∣ b s|b s ∣ b 。因此s s s 为a , b a,b a , b 的公因子,d ≥ s d\geq s d ≥ s 。
∵ d ∣ a , d ∣ b , s = a x + b y \because d|a,d|b,s=ax+by ∵ d ∣ a , d ∣ b , s = a x + b y ,∴ d ∣ s \therefore d|s ∴ d ∣ s ,d ≤ s d\leq s d ≤ s 。
因此d = s d=s d = s ,命题得证。
推论1 :( a , b ) = 1 (a,b)=1 ( a , b ) = 1 的充分必要条件是∃ x , y ∈ Z , s . t . a x + b y = 1 \exist x,y\in Z, s.t.~~ax+by=1 ∃ x , y ∈ Z , s . t . a x + b y = 1 。
推论2 :裴蜀等式也可以用来给最大公约数定义:d d d 其实就是最小的可以写成a x + b y ax + by a x + b y 形式的正整数。
推论2 :设a 1 , a 2 , . . . , a n a_1,a_2,...,a_n a 1 , a 2 , . . . , a n 为n n n 个整数,d d d 是它们的最大公约数,那么∃ x 1 , x 2 , . . . , x n \exist x_1,x_2,...,x_n ∃ x 1 , x 2 , . . . , x n ,使得a 1 x 1 + a 2 x 2 + . . . + a n x n = d a_1x_1+a_2x_2+...+a_nx_n=d a 1 x 1 + a 2 x 2 + . . . + a n x n = d 成立。特别的,若a 1 , a 2 , . . . , a n a_1,a_2,...,a_n a 1 , a 2 , . . . , a n 是互质的(不是两两互质),那么∃ x 1 , x 2 , . . . , x n \exist x_1,x_2,...,x_n ∃ x 1 , x 2 , . . . , x n ,使得a 1 x 1 + a 2 x 2 + . . . + a n x n = 1 a_1x_1+a_2x_2+...+a_nx_n=1 a 1 x 1 + a 2 x 2 + . . . + a n x n = 1 成立。
2. 扩展欧几里得算法
如何用扩展欧几里得算法求裴蜀数?
假如要求解的不定方程(又名丢番图方程)为a x + b y = m ax+by=m a x + b y = m
我们现在先求解不定方程a x + b y = d ax+by=d a x + b y = d ,其中d = ( a , b ) d=(a,b) d = ( a , b ) ,d ∣ m d|m d ∣ m 。
由欧几里得算法性质1,2可知( a , b ) = ( b , a m o d b ) = ( b , a − ⌊ a b ⌋ × b ) (a,b)=(b,a~mod~b)=(b, a-\lfloor \frac{a}{b} \rfloor\times b) ( a , b ) = ( b , a m o d b ) = ( b , a − ⌊ b a ⌋ × b )
若a x 1 + b y 1 = d ax_1+by_1=d a x 1 + b y 1 = d 有解,则b y 2 + ( a m o d b ) x 2 = d by_2+(a~mod~b)x_2=d b y 2 + ( a m o d b ) x 2 = d 一定有解,即b y 2 + ( a − ⌊ a b ⌋ × b ) x 2 = d by_2+(a-\lfloor \frac{a}{b} \rfloor\times b)x_2=d b y 2 + ( a − ⌊ b a ⌋ × b ) x 2 = d 有解。
化简得a x 2 + b ( y 2 − ⌊ a b ⌋ × x 2 ) = d ax_2+b(y_2-\lfloor \frac{a}{b} \rfloor\times x_2)=d a x 2 + b ( y 2 − ⌊ b a ⌋ × x 2 ) = d ,那么x 1 = x 2 , y 1 = y 2 − ⌊ a b ⌋ × x 2 x_1=x_2,y_1=y_2-\lfloor \frac{a}{b} \rfloor\times x_2 x 1 = x 2 , y 1 = y 2 − ⌊ b a ⌋ × x 2 。
于是可以递归进行操作,当b ′ = 0 b'=0 b ′ = 0 时,( a ′ , 0 ) = d (a',0)=d ( a ′ , 0 ) = d ,也就是a ′ = d a'=d a ′ = d ,此时x = 1 , y = 0 x=1,y=0 x = 1 , y = 0 为一组平凡解,再不断带回,即可得到不定方程的一组特解,设此特解为{ x 1 , y 1 } \{x_1,y_1\} { x 1 , y 1 } 。则通解即为{ x 1 + k × b d , y 1 − k × a d , k ∈ Z } \{x_1+k\times\frac{b}{d}, y_1-k\times\frac{a}{d}, k\in \Z\} { x 1 + k × d b , y 1 − k × d a , k ∈ Z }
证明通解是方程的解:
a x + b y = a × ( x 1 + k × b d ) + b × ( y 1 − k × a d ) = a x 1 + b y 1 + k × ( a b d − a b d ) = d ax+by=a\times(x_1+k\times\frac{b}{d})+b\times(y_1-k\times\frac{a}{d})\\=ax_1+by_1+k\times (\frac{ab}{d}-\frac{ab}{d})=d a x + b y = a × ( x 1 + k × d b ) + b × ( y 1 − k × d a ) = a x 1 + b y 1 + k × ( d a b − d a b ) = d
因此,通解是可以使等式成立的。
那么同理,a x + b y = m ax+by=m a x + b y = m ,通解也是{ x 0 + k × b d , y 0 − k × a d } \{x_0+k\times \frac{b}{d}, y_0-k\times \frac{a}{d}\} { x 0 + k × d b , y 0 − k × d a } 。
1 2 3 4 5 6 7 8 9 int exgcd (int a, int b, int &x, int &y) { if (!b){ x = 1 , y = 0 ; return a; } int d = exgcd (b, a%b, y, x); y -= a / b * x; return d; }
同余
1. 定义
如果a , b ∈ Z a,b\in\Z a , b ∈ Z ,而m m m 是一个固定的正整数,则当m ∣ ( a − b ) m|(a-b) m ∣ ( a − b ) 时,我们就说a , b a,b a , b 对模m m m 同余,记作a ≡ b ( m o d m ) a\equiv b(\mod m) a ≡ b ( m o d m ) ,当m m m 不能整除a − b a-b a − b 时,我们就说a , b a,b a , b 对模m m m 不同余,记作a ≢ b ( m o d m ) a \not\equiv b(\mod m) a ≡ b ( m o d m ) 。
2. 运算法则
{ A × B m o d P = ( A m o d P × B m o d P ) m o d P ( A + B ) m o d P = ( A m o d P + B m o d P ) m o d P ( A − B ) m o d P = ( A m o d P − B m o d P + P ) m o d P 令 c n t ( x ) = _ _ b u l i t i n _ p o p c o u n t ( x ) , 则 c n t ( A ⊕ B ) m o d 2 = c n t ( A ) m o d 2 ⊕ c n t ( B ) m o d 2 A ≡ B ( m o d P ) → A n ≡ B n m o d m \begin{cases} A\times B~mod ~P = (A ~mod ~P \times B ~mod ~P) \mod P\\
(A + B)~ mod~ P = (A ~mod ~P + B ~mod ~P) \mod P \\
(A - B)~ mod ~P = (A ~mod~ P - B ~mod ~P + P) \mod P \\
令cnt(x)=\_\_bulitin\_popcount(x), 则\\
cnt(A \oplus B) ~mod ~2 = cnt(A)~mod 2~ \oplus cnt(B)\mod 2 \\
A\equiv B(mod~P) \rightarrow A^n\equiv B^n \mod m
\end{cases}
⎩ ⎪ ⎪ ⎪ ⎪ ⎪ ⎪ ⎪ ⎪ ⎨ ⎪ ⎪ ⎪ ⎪ ⎪ ⎪ ⎪ ⎪ ⎧ A × B m o d P = ( A m o d P × B m o d P ) m o d P ( A + B ) m o d P = ( A m o d P + B m o d P ) m o d P ( A − B ) m o d P = ( A m o d P − B m o d P + P ) m o d P 令 c n t ( x ) = _ _ b u l i t i n _ p o p c o u n t ( x ) , 则 c n t ( A ⊕ B ) m o d 2 = c n t ( A ) m o d 2 ⊕ c n t ( B ) m o d 2 A ≡ B ( m o d P ) → A n ≡ B n m o d m
3. 基本概念
(1) 剩余类 完全剩余系 和 简化剩余系
剩余类 :也叫同余类,设模为n n n ,则根据余数可将所有的整数分为n n n 类,把所有和整数a a a 模n n n 同余的整数构成的集合叫做模n n n 的一个剩余类,记作[ a ] [a] [ a ] ,并把a a a 叫做剩余类[ a ] [a] [ a ] 的一个代表元。
完全剩余系 :从模n n n 的每个剩余类中各取一个数,得到一个由n n n 个数组成的集合,叫做模n n n 的一个完全剩余系。最常用的完全剩余系是{ 0 , 1 , . . . , n − 1 } \{0,1,...,n-1\} { 0 , 1 , . . . , n − 1 } 。
简化剩余系 :也称既约剩余系或缩系,是n n n 的完全剩余系中与n n n 互质的数构成的子集。如果模n n n 的一个剩余类里所有数都与n n n 互质,就把它叫做与模n n n 互质的剩余类。在与模n n n 互质的全体剩余类中,从每个类中各任取一个数作为代表组成的集合,叫做模n n n 的一个简化剩余系。
4. 常用小定理
**定理1:**如果a , b , c a,b,c a , b , c 是任意三个整数,m m m 是一个正整数且( m , c ) = 1 (m,c)=1 ( m , c ) = 1 ,则当a c ≡ b c ( m o d m ) ac\equiv bc(\mod m) a c ≡ b c ( m o d m ) 时,有a ≡ b ( m o d m ) a\equiv b(\mod m) a ≡ b ( m o d m ) 。
证明:由于c ( a − b ) = a c − b c = m q c(a-b)=ac-bc=mq c ( a − b ) = a c − b c = m q ,其中q ∈ Z q\in \Z q ∈ Z ,( m , c ) = 1 (m,c)=1 ( m , c ) = 1 。我们有a − b = m q c = m q 1 a-b=m\frac{q}{c}=mq_1 a − b = m c q = m q 1 。
**定理2:**满足a x ≡ 1 ( m o d m ) a^{x}\equiv 1(\mod m) a x ≡ 1 ( m o d m ) 的最小正整数x x x ,一定是φ ( m ) \varphi(m) φ ( m ) 的约数,即x ∣ φ ( m ) x|\varphi(m) x ∣ φ ( m ) 。
反证法。假设x ∤ φ ( m ) , φ ( m ) = q x + r , r ∈ ( 0 , x ) x\not\mid \varphi(m),\varphi(m)=qx+r, r\in(0,x) x ∣ φ ( m ) , φ ( m ) = q x + r , r ∈ ( 0 , x ) 。
则有a q x ≡ 1 q ( m o d m ) a^{qx}\equiv 1^q(\mod m) a q x ≡ 1 q ( m o d m ) ,a q x + r ≡ 1 ( m o d m ) a^{qx+r}\equiv 1(\mod m) a q x + r ≡ 1 ( m o d m )
a r ≡ 1 ( m o d m ) a^r\equiv 1(\mod m) a r ≡ 1 ( m o d m ) ,r < x r<x r < x ,又x x x 是满足同余式的最小正整数,矛盾。得证。
定理3 :∃ x > 0 \exist x>0 ∃ x > 0 ,使得a x ≡ 1 ( m o d m ) a^x\equiv 1(\mod m) a x ≡ 1 ( m o d m ) 成立,它的充分必要条件为( a , m ) = 1 (a,m)=1 ( a , m ) = 1 。
必要性:即欧拉定理a φ ( m ) ≡ 1 ( m o d m ) a^{\varphi(m)}\equiv 1(\mod m) a φ ( m ) ≡ 1 ( m o d m ) 的证明;
充分性:(反证法)若a x ≡ 1 ( m o d m ) , x > 0 a^x\equiv 1(\mod m),x>0 a x ≡ 1 ( m o d m ) , x > 0 ,则a x + k × m = 1 a^x+k\times m=1 a x + k × m = 1 ,若( a , m ) = w > 1 (a,m)=w>1 ( a , m ) = w > 1 ,则a x + k × m = w × a x + k × m w ≥ w > 1 a^x+k\times m=w\times\frac{a^x+k\times m}{w}\geq w>1 a x + k × m = w × w a x + k × m ≥ w > 1 。矛盾,得证。
同余这块,有非常多乱七八糟的构造方法,非常偏数学,甚至可以做到《初等数论》上的题目……只能说注意积累,大胆乱猜 。
初等数论四大定理
1. 威尔逊定理
(1) 结论
当且仅当p p p 为素数时,( p − 1 ) ! ≡ − 1 ( m o d p ) (p-1)!\equiv -1(\mod p) ( p − 1 ) ! ≡ − 1 ( m o d p ) 。
(2) 证明
充分性 :若p p p 不为素数,则( p − 1 ) ! ≢ − 1 ( m o d p ) (p-1)!\not\equiv -1(\mod p) ( p − 1 ) ! ≡ − 1 ( m o d p ) 。
必要性 :若p p p 为素数,则一定有( p − 1 ) ! ≡ − 1 ( m o d p ) (p-1)!\equiv -1(\mod p) ( p − 1 ) ! ≡ − 1 ( m o d p ) 。
p = 2 p=2 p = 2 时,结论显然成立。
若p p p 为奇素数,取集合A = { 1 , 2 , 3 , . . , p − 1 } A=\{1,2,3,..,p-1\} A = { 1 , 2 , 3 , . . , p − 1 } ,A A A 构成模p p p 乘法的简化剩余系,即∀ i ∈ A , ∃ j ∈ A \forall i\in A,\exist j\in A ∀ i ∈ A , ∃ j ∈ A ,使得i × j ≡ 1 ( m o d p ) i\times j\equiv 1(\mod p) i × j ≡ 1 ( m o d p ) 。
证明:因为A A A 构成模p p p 乘法的取值的集合了(除了0),所以这个结论肯定成立。
那么,这p − 1 p-1 p − 1 个数是不是两两配对的呢?首先,由同余的运算法则,可以得到一定没有i j ≡ i k ( m o d p ) , j ≠ k ij\equiv ik(\mod p), j\neq k i j ≡ i k ( m o d p ) , j = k 的情况,那么它一定是两个一对,或者单独一个的情况。考虑x 2 ≡ 1 ( m o d p ) x^2\equiv 1(\mod p) x 2 ≡ 1 ( m o d p ) ,解得x ≡ 1 ( m o d p ) x\equiv 1(\mod p) x ≡ 1 ( m o d p ) ,或者x ≡ p − 1 ( m o d p ) x\equiv p-1(\mod p) x ≡ p − 1 ( m o d p ) ,那么其余则两两配对。因此( p − 1 ) ! ≡ 1 × ( p − 1 ) ≡ − 1 ( m o d p ) (p-1)!\equiv 1\times (p-1)\equiv -1(\mod p) ( p − 1 ) ! ≡ 1 × ( p − 1 ) ≡ − 1 ( m o d p ) ,得证。
(5)例题
可以看下它的考察方式:UVA1434 YAPTCHA ,主要就是欧拉筛+威尔逊定理+前缀和
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 #include <bits/stdc++.h> using namespace std;#define ll long long int n, cnt;const int N = 3e6 + 20 ;bool st[N];int p[N], pre[N];void Euler (int n) { for (int i=2 ;i<=n;++i){ if (!st[i]) p[++cnt] = i; for (int j=1 ;p[j]<=n/i;++j){ st[i * p[j]] = true ; if (i % p[j] == 0 ) break ; } } } void init () { Euler (N - 1 ); for (int i=1 ;i<N;++i){ int x = 0 ; if (i > 7 && (i - 7 ) % 3 == 0 && !st[i]){ x = 1 ; } pre[i] = pre[i - 1 ] + x; } } int main (void ) { init (); int t; scanf ("%d" , &t); while (t--){ scanf ("%d" , &n); printf ("%d\n" , pre[n * 3 + 7 ]); } return 0 ; }
2. 欧拉定理
(1) 结论
若m m m 是一个大于1的整数,且满足条件( a , m ) = 1 (a,m)=1 ( a , m ) = 1 ,则我们有
a φ ( m ) ≡ 1 ( m o d m ) a^{\varphi(m)}\equiv 1(\mod m)
a φ ( m ) ≡ 1 ( m o d m )
(2) 证明
引理1: 设m m m 是一个大于1的整数,a a a 是一个整数且满足( a , m ) = 1 (a,m)=1 ( a , m ) = 1 ,如果B = { b 1 , b 2 , . . , b φ ( m ) } B=\{b_1,b_2,..,b_{\varphi(m)}\} B = { b 1 , b 2 , . . , b φ ( m ) } 是模m m m 的一个简化剩余系,则B ′ = { a b 1 , a b 2 , . . , a b φ ( m ) } B'=\{ab_1,ab_2,..,ab_{\varphi(m)}\} B ′ = { a b 1 , a b 2 , . . , a b φ ( m ) } 也是模m m m 的一个简化剩余系。
证明:由于从B B B 中取出任何一个正整数b i b_i b i ,都有( b i , m ) = 1 (b_i,m)=1 ( b i , m ) = 1 。又( a , m ) = 1 (a,m)=1 ( a , m ) = 1 ,可以得到在B ′ B' B ′ 中任意取出一个整数a b i ab_i a b i ,都有( a b i , m ) = 1 (ab_i,m)=1 ( a b i , m ) = 1 。那么,现在利用反证法,假设在B ′ B' B ′ 中存在两个整数a b k , a b λ ab_k,ab_{\lambda} a b k , a b λ ,1 ≤ k < λ ≤ φ ( m ) 1\leq k < \lambda \leq \varphi(m) 1 ≤ k < λ ≤ φ ( m ) ,使得:a b k ≡ a b λ ( m o d m ) ab_k\equiv ab_{\lambda}(\mod m) a b k ≡ a b λ ( m o d m ) 成立。
又( a , m ) = 1 (a,m)=1 ( a , m ) = 1 ,故b k ≡ b λ ( m o d m ) b_k\equiv b_{\lambda}(\mod m) b k ≡ b λ ( m o d m ) ,由于b k , b λ b_k,b_{\lambda} b k , b λ 是简化剩余系的元素,因此不可能模m m m 同余,所以同余等式不可能成立,得证。
下面正式的证明欧拉定理。
考虑模m m m 的最小正缩系A A A ,即A = { 1 , a 2 , . . . , a φ ( m ) } A=\{1,a_2,...,a_{\varphi(m)}\} A = { 1 , a 2 , . . . , a φ ( m ) } ,是不大于m m m 且和m m m 互质的全体正整数,令r 1 r_1 r 1 是一个整数,满足条件:$ a \times 1\equiv r_1(\mod m), r_1\in[0,m-1]$
令r i r_i r i (其中i = 2 , . . , φ ( m ) i=2,..,\varphi(m) i = 2 , . . , φ ( m ) )是一个整数,满足条件:a a i ≡ r i ( m o d m ) , r i ∈ [ 0 , m − 1 ] aa_i\equiv r_i(\mod m),r_i\in[0,m-1] a a i ≡ r i ( m o d m ) , r i ∈ [ 0 , m − 1 ] 。
则我们有a ≡ r 1 ( m o d m ) , a a 2 ≡ r 2 ( m o d m ) , . . , a a φ ( m ) ≡ r φ ( m ) ( m o d m ) a\equiv r_1(\mod m),aa_2\equiv r_2(\mod m),..,aa_{\varphi(m)}\equiv r_{\varphi(m)}(\mod m) a ≡ r 1 ( m o d m ) , a a 2 ≡ r 2 ( m o d m ) , . . , a a φ ( m ) ≡ r φ ( m ) ( m o d m )
由于A A A 是模m m m 的一个简化剩余系,并且( a , m ) = 1 (a,m)=1 ( a , m ) = 1 ,因此R = { r 1 , r 2 , . . , r φ ( m ) } R=\{r_1,r_2,..,r_{\varphi(m)}\} R = { r 1 , r 2 , . . , r φ ( m ) } 也是一个简化剩余系,并且和A A A 至少在次序上可能有不同,故得到∏ i = 1 φ ( m ) r i = ∏ i = 1 φ ( m ) a i \prod_{i=1}^{\varphi(m)}r_i=\prod_{i=1}^{\varphi(m)}a_i ∏ i = 1 φ ( m ) r i = ∏ i = 1 φ ( m ) a i
因此∏ i = 1 φ ( m ) ( a × a i ) ≡ ∏ i = 1 φ ( m ) a i ( m o d m ) \prod_{i=1}^{\varphi(m)}(a\times a_i) \equiv \prod_{i=1}^{\varphi(m)}a_i (\mod m) ∏ i = 1 φ ( m ) ( a × a i ) ≡ ∏ i = 1 φ ( m ) a i ( m o d m )
即a φ ( m ) ≡ 1 ( m o d m ) a^{\varphi(m)}\equiv 1(\mod m) a φ ( m ) ≡ 1 ( m o d m ) 。
(3) 推论
欧拉降幂:
A b ≡ A b m o d φ ( m ) ( m o d m ) A^b\equiv A^{b\mod \varphi(m)} (\mod m) A b ≡ A b m o d φ ( m ) ( m o d m )
证明:设b = k × φ ( m ) + r b=k\times \varphi(m)+r b = k × φ ( m ) + r ,则r = b m o d φ ( m ) r=b\mod \varphi(m) r = b m o d φ ( m ) 。
A b ≡ A r × ( A φ ( m ) ) k ≡ A r ( m o d m ) A^b\equiv A^r\times (A^{\varphi(m)})^k\equiv A^r(\mod m) A b ≡ A r × ( A φ ( m ) ) k ≡ A r ( m o d m ) ,得证。
3. 费马小定理
(1) 结论
事实上,费马小定理就是欧拉定理的一种特殊情况。
如果p p p 为质数,p ∤ a p\nmid a p ∤ a ,则我们有a p − 1 ≡ 1 ( m o d p ) a^{p-1} \equiv 1 (\mod p) a p − 1 ≡ 1 ( m o d p )
(1) 证明
由于p p p 为质数,因此φ ( p ) = p − 1 \varphi(p)=p-1 φ ( p ) = p − 1 ,取欧拉定理中m = p m=p m = p ,即得到费马小定理。
需要注意的是,∃ m ∈ N + , a ∈ Z \exist m\in \N^+,a\in \Z ∃ m ∈ N + , a ∈ Z ,使得m ∤ a m \nmid a m ∤ a ,这里m m m 不是素数,使得a m − 1 ≡ 1 ( m o d m ) a^{m-1}\equiv 1(\mod m) a m − 1 ≡ 1 ( m o d m ) ,即费马小定理的逆定理不成立。比如,1024 ≡ 1 ( m o d 341 ) 1024\equiv 1(\mod 341) 1 0 2 4 ≡ 1 ( m o d 3 4 1 ) ,2 340 ≡ ( 2 10 ) 34 ≡ ( 1024 ) 34 ≡ 1 ( m o d 341 ) 2^{340}\equiv (2^{10})^{34}\equiv (1024)^{34}\equiv 1(\mod 341) 2 3 4 0 ≡ ( 2 1 0 ) 3 4 ≡ ( 1 0 2 4 ) 3 4 ≡ 1 ( m o d 3 4 1 ) ,而341 = 11 × 31 341=11\times 31 3 4 1 = 1 1 × 3 1 ,不是素数。
(2) 推论
明显的,当p p p 为质数,且p ∤ a p \nmid a p ∤ a 时,a p − 2 ≡ a − 1 ( m o d p ) a^{p-2}\equiv a^{-1} (\mod p) a p − 2 ≡ a − 1 ( m o d p ) ,由此就可以求得在模p p p 意义下的乘法逆元。
欧拉定理和费马定理很难单独考察,大部分都是作为解题的一个步骤出现。
4. 中国剩余定理
1) 中国剩余定理(crt)
定理 :
有一元线性同余方程组( S ) (S) ( S ) 如下。
( S ) : { x ≡ a 1 ( m o d m 1 ) x ≡ a 2 ( m o d m 2 ) . . . x ≡ a n ( m o d m n ) (S):\begin{cases} & x\equiv a_1~(\mod m_1) \\ & x\equiv a_2~(\mod m_2) \\ &... \\ & x\equiv a_n~(\mod m_n) \end{cases} ( S ) : ⎩ ⎪ ⎪ ⎪ ⎨ ⎪ ⎪ ⎪ ⎧ x ≡ a 1 ( m o d m 1 ) x ≡ a 2 ( m o d m 2 ) . . . x ≡ a n ( m o d m n )
假设整数m 1 , m 2 , . . . , m n m_1,m_2,...,m_n m 1 , m 2 , . . . , m n 两两互质,则对任意的整数a 1 , a 2 , . . . , a n a_1,a_2,...,a_n a 1 , a 2 , . . . , a n ,方程组( S ) (S) ( S ) 有解,并且通解可以用如下方式构造得到:
设M = ∏ i = 1 n m i M=\prod \limits _{i=1} ^{n} m_i M = i = 1 ∏ n m i ,M i = M m i M_i=\frac{M}{m_i} M i = m i M ,设t i = M i − 1 t_i=M_i^{-1} t i = M i − 1 ,为M i M_i M i 在模m i m_i m i 意义下的M i M_i M i 的模m i m_i m i 乘法逆元。
方程组( S ) (S) ( S ) 的通解形式为x = a 1 t 1 M 1 + a 2 t 2 M 2 + . . . + a n t n M n + k M = k M + ∑ i = 1 n a i t i M i , k ∈ Z x=a_1t_1M_1+a_2t_2M_2+...+a_nt_nM_n+kM=kM+\sum\limits_{i=1}^na_it_iM_i, k\in \Z x = a 1 t 1 M 1 + a 2 t 2 M 2 + . . . + a n t n M n + k M = k M + i = 1 ∑ n a i t i M i , k ∈ Z 。
在模M M M 的意义下,方程组( S ) (S) ( S ) 只有一个解:x = ( ∑ i = 1 n a i t i M i ) m o d M x=(\sum\limits_{i=1}^na_it_iM_i)\mod M x = ( i = 1 ∑ n a i t i M i ) m o d M
namo 首先解的每一项,都是可以求出来的,t i t_i t i 也可以通过扩展欧几里得算法求出。然后再来小小的证明下!
证明 :
首先证明x x x 是方程组( S ) (S) ( S ) 的一个解。
对于解的每一项,可以看出a i t i M i ≡ a i × 1 ≡ a i ( m o d m i ) a_it_iM_i\equiv a_i\times 1 \equiv a_i(\mod m_i~) a i t i M i ≡ a i × 1 ≡ a i ( m o d m i ) ,
a i t i M i ≡ 0 ( m o d m j ) a_it_iM_i\equiv 0~(\mod m_j~) a i t i M i ≡ 0 ( m o d m j ) ,∀ j ∈ [ 1 , n ] , j ≠ i \forall j\in[1,n], j\neq i ∀ j ∈ [ 1 , n ] , j = i
因此,x x x 满足x = a i t i M i + ∑ j ≠ i a j t j M j ≡ a i + ∑ j ≠ i 0 ≡ a i ( m o d m i ) , ∀ i ∈ [ 1 , n ] x=a_it_iM_i+\sum\limits _{j\neq i}a_jt_jM_j \equiv a_i+\sum\limits _{j\neq i}0 \equiv a_i~(\mod m_i), ~~\forall i\in[1,n] x = a i t i M i + j = i ∑ a j t j M j ≡ a i + j = i ∑ 0 ≡ a i ( m o d m i ) , ∀ i ∈ [ 1 , n ]
故x x x 是方程组的一个解。
然后证明在模M M M 的意义下,方程组( S ) (S) ( S ) 只有一个解。
假设x 1 , x 2 x_1,x_2 x 1 , x 2 都是方程组( S ) (S) ( S ) 的解,那么x 1 − x 2 ≡ 0 ( m o d m i ) , ∀ i ∈ [ 1 , n ] x_1-x_2\equiv 0~(\mod m_i), \forall i\in [1,n] x 1 − x 2 ≡ 0 ( m o d m i ) , ∀ i ∈ [ 1 , n ]
而m 1 , m 2 , . . . , m n m_1,m_2,...,m_n m 1 , m 2 , . . . , m n 两两互质,这说明M ∣ ( x 1 − x 2 ) M|(x_1-x_2) M ∣ ( x 1 − x 2 ) ,所以方程组( S ) (S) ( S ) 的任何两个解之间必然相差M M M 的整数倍,所以方程组所有的解的集合就是{ k M + ∑ i = 1 n a i t i M i ; k ∈ Z } \{kM+\sum \limits _{i=1} ^n a_it_iM_i;k\in \Z\} { k M + i = 1 ∑ n a i t i M i ; k ∈ Z }
所以在模M M M 的意义下,方程组( S ) (S) ( S ) 只有一个解。
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 int n;const int N = 10 + 5 ;int A[N], B[N];ll mod (ll a, ll b) { return (a % b + b) % b; } ll exgcd (ll a, ll b, ll &x, ll &y) { if (!b){ x = 1 , y = 0 ; return a; } ll d = exgcd (b, a % b, y, x); y -= a / b * x; return d; } int main (void ) { scanf ("%d" , &n); ll M = 1 , res = 0 , ti, y; for (int i=1 ;i<=n;++i){ scanf ("%d%d" , &A[i], &B[i]); M = M * A[i]; } for (int i=1 ;i<=n;++i){ ll Mi = M / A[i]; exgcd (Mi, A[i], ti, y); res = mod (res + Mi * ti * B[i], M); } printf ("%lld\n" , res); return 0 ; }
2) 扩展中国剩余定理(excrt)
(rxz的题解写的太好了,受益良多QAQ,他讲的东西真的都是清晰细致又简单 但是因为他用python所以不推mod 岂可修)
https://www.luogu.com.cn/blog/blue/kuo-zhan-zhong-guo-sheng-yu-ding-li
这里自己再重新推导一遍。
修改条件,使得m 1 , m 2 , . . . , m n m_1,m_2,...,m_n m 1 , m 2 , . . . , m n 不再两两互质,此时应该如何求方程组的解?
( S ) : { x ≡ a 1 ( m o d m 1 ) x ≡ a 2 ( m o d m 2 ) . . . x ≡ a n ( m o d m n ) (S):\begin{cases} & x\equiv a_1~(\mod m_1) \\ & x\equiv a_2~(\mod m_2) \\ &... \\ & x\equiv a_n~(\mod m_n) \end{cases} ( S ) : ⎩ ⎪ ⎪ ⎪ ⎨ ⎪ ⎪ ⎪ ⎧ x ≡ a 1 ( m o d m 1 ) x ≡ a 2 ( m o d m 2 ) . . . x ≡ a n ( m o d m n ) x = { k M + ∑ i = 1 n a i t i M i ; k ∈ Z } x=\{kM+\sum \limits _{i=1} ^n a_it_iM_i;k\in \Z\} x = { k M + i = 1 ∑ n a i t i M i ; k ∈ Z }
互质条件不满足时,t i t_i t i 不存在。(回忆逆元的定义 ,只有当( b , m ) = 1 (b,m)=1 ( b , m ) = 1 时才存在b − 1 ( m o d m ) b^{-1}(\mod m) b − 1 ( m o d m ) ),那么不能利用公式时,只能尝试去不断的合并方程,直到n n n 个方程仅剩下一个,再使用扩展欧几里得算法求解唯一的同余方程。那么,如何将两个方程等价的变换为一个方程呢?考虑如下情况:
{ x ≡ a 1 ( m o d m 1 ) x ≡ a 2 ( m o d m 2 ) \begin{cases} & x\equiv a_1(\mod m_1) \\ & x\equiv a_2(\mod m_2)\end{cases} { x ≡ a 1 ( m o d m 1 ) x ≡ a 2 ( m o d m 2 )
此方程组等价于x = k 1 m 1 + a 1 = k 2 m 2 + a 2 x = k_1m_1+a_1=k_2m_2+a_2 x = k 1 m 1 + a 1 = k 2 m 2 + a 2
移项后,得k 1 m 1 − k 2 m 2 = a 2 − a 1 k_1m_1-k_2m_2=a_2-a_1 k 1 m 1 − k 2 m 2 = a 2 − a 1 。
令a = m 1 , b = m 2 , m = a 2 − a 1 a=m_1,b=m_2,m=a_2-a_1 a = m 1 , b = m 2 , m = a 2 − a 1 ,方程就变成了我们最熟悉的不定方程。那么,记d = ( m 1 , m 2 ) d=(m_1,m_2) d = ( m 1 , m 2 ) ,求是否有x , y x,y x , y ,满足a x + b y = d ax+by=d a x + b y = d 。求解的步骤即是:先用裴蜀定理判断是否有解,再用扩展欧几里得算法求解(系)。则k 1 = x × a 2 − a 1 d , k 2 = − y × a 2 − a 1 d k_1=x\times \frac{a_2-a_1}{d},k_2=-y\times \frac{a_2-a_1}{d} k 1 = x × d a 2 − a 1 , k 2 = − y × d a 2 − a 1 ,这就是方程的一组特解。为了避免数据溢出,可以让k 1 : = k 1 m o d m 2 d k_1:=k_1 \mod \frac{m_2}{d} k 1 : = k 1 m o d d m 2 (具体证明见裴蜀定理),那么,x = a 1 + k 1 m 1 x=a_1+k_1m_1 x = a 1 + k 1 m 1 ,就求出来了两个方程的特解。设这个特解为r r r ,那么它的通解就是{ r + k × L C M , k ∈ Z } \{r+k\times LCM, k\in \Z\} { r + k × L C M , k ∈ Z } ,那么,就可以合并两个同余方程啦!
x ≡ r ( m o d L C M ) x\equiv r~(\mod LCM) x ≡ r ( m o d L C M )
这样不断合并,直到只剩下一个方程,解出答案即可。注意,中间非常容易数据溢出!即使是long long!所以最好直接用一个mul函数,一边做乘法一边做模运算。
横线中的这段是rxz的证明,嘛嘛,就相当于方程a x + b y = d ax+by=d a x + b y = d 两边÷ d \div d ÷ d ,结果是一样的。
那么,记d = ( m 1 , m 2 ) d=(m_1,m_2) d = ( m 1 , m 2 ) ,p 1 = m 1 d , p 2 = m 2 d p_1=\frac{m_1}{d}, p_2=\frac{m_2}{d} p 1 = d m 1 , p 2 = d m 2 ,易得( p 1 , p 2 ) = 1 (p_1,p_2)=1 ( p 1 , p 2 ) = 1 。
方程可被写为这样的形式:k 1 p 1 − k 2 p 2 = a 2 − a 1 d k_1p_1-k_2p_2=\frac{a_2-a_1}{d} k 1 p 1 − k 2 p 2 = d a 2 − a 1
由于当d ∣ ( a 2 − a 1 ) d|(a_2-a_1) d ∣ ( a 2 − a 1 ) 时,方程才有解,因此右边的式子为整数。接着按照exgcd的流程走,我们求这个方程的解
λ 1 p 1 + λ 2 p 2 = 1 \lambda_1p_1+\lambda_2p_2=1 λ 1 p 1 + λ 2 p 2 = 1 ,则{ k 1 = a 2 − a 1 d × λ 1 k 2 = a 2 − a 1 d × λ 2 \begin{cases} & k_1=\frac{a_2-a_1}{d} \times \lambda_1\\ & k_2=\frac{a_2-a_1}{d} \times \lambda_2\end{cases} { k 1 = d a 2 − a 1 × λ 1 k 2 = d a 2 − a 1 × λ 2
将结果代入方程,得x = a 1 + k 1 m 1 = a 1 + a 2 − a 1 d λ 1 m 1 x=a_1+k_1m_1=a_1+\frac{a_2-a_1}{d}\lambda_1m_1 x = a 1 + k 1 m 1 = a 1 + d a 2 − a 1 λ 1 m 1 。
这个x x x 就是方程的一个特解。
那么,进一步的,如何求出整个解系呢?
**定理:**若有特解x ′ x' x ′ ,令L C M = l c m ( m 1 , m 2 ) LCM=lcm(m_1,m_2) L C M = l c m ( m 1 , m 2 ) ,则{ x ≡ a 1 ( m o d m 1 ) x ≡ a 2 ( m o d m 2 ) \begin{cases} & x\equiv a_1(\mod m_1) \\ & x\equiv a_2(\mod m_2)\end{cases} { x ≡ a 1 ( m o d m 1 ) x ≡ a 2 ( m o d m 2 ) 的通解为x = { k × L C M + x ′ ; k ∈ Z } x=\{k\times LCM+x';k\in \Z\} x = { k × L C M + x ′ ; k ∈ Z } 。
该定理的证明和CRT的证明完全相同。好累,我不想重抄一遍所以就这样吧
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 int n;const int N = 1e5 + 10 ;ll a1, m1, a[N], m[N], res; ll mul (ll a, ll b, ll M) { ll res = 0 ; while (b > 0 ){ if (b & 1 ) res = (res + a) % M; a = (a + a) % M; b >>= 1 ; } return res; } ll merge (ll a2, ll m2) { ll x, y, M, c = mod (a2 - a1, m2); ll d = exgcd (m1, m2, x, y); if (c % d){ return -1 ; } M = (m1 / d * m2); ll k1 = mod (mul (x, c / d, m2 / d), m2 / d); ll r = mod (k1 * m1 + a1, M); a1 = r, m1 = M; return r; } int main (void ) { scanf ("%d" , &n); scanf ("%lld%lld" , &m1, &a1); if (n == 1 ){ printf ("%lld\n" , mod (a1, m1)); return 0 ; } for (int i=1 ;i<n;++i){ scanf ("%lld%lld" , &m[i], &a[i]); } for (int i=1 ;i<n;++i){ res = merge (a[i], m[i]); if (res < 0 ) break ; } printf ("%lld\n" , res); return 0 ; }
高斯消元解线性方程组
这一部分的原理参考线性代数即可。
枚举每一列c c c
找到绝对值最大的一行,将该行换到最上面;
将该行第1个数变成1,将下面所有行的第c c c 列消成0;
时间复杂度:对于一个有n n n 个未知数,有n n n 个方程的方程组,时间复杂度为O ( n 3 ) O(n^3) O ( n 3 )
代码
(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 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 #define ZERO 1e-8 int n;const int N = 100 + 10 ;double a[N][N]; void printA () { for (int i=0 ;i<n;++i){ for (int j=0 ;j<n+1 ;++j){ printf ("%.2lf " , a[i][j]); } puts ("" ); } puts ("" ); } int gauss () { int c = 0 , r = 0 ; for (c=0 ;c<n;++c){ int t = r; for (int i=r;i<n;++i){ if (fabs (a[i][c]) > fabs (a[t][c])) t = i; } if (fabs (a[t][c]) < ZERO) continue ; for (int i=c;i<=n;++i) swap (a[t][i], a[r][i]); for (int i=n;i>=c;--i) a[r][i] /= a[r][c]; for (int i=r+1 ;i<n;++i){ if (fabs (a[i][c]) > ZERO){ for (int j=n;j>=c;--j){ a[i][j] -= a[r][j] * a[i][c]; } } } ++r; } if (r < n){ for (int i=r;i<n;++i){ if (fabs (a[i][n]) > ZERO){ return 2 ; } } return 1 ; } for (int i=n-1 ;i>=0 ;--i){ for (int j=i+1 ;j<n;++j){ a[i][n] -= a[i][j] * a[j][n]; } } return 0 ; } int main (void ) { scanf ("%d" , &n); for (int i=0 ;i<n;++i){ for (int j=0 ;j<n+1 ;++j){ scanf ("%lf" , &a[i][j]); } } int t = gauss (); if (t == 2 ){ puts ("No solution" ); return 0 ; } else if (t == 1 ){ puts ("Infinite group solutions" ); return 0 ; } for (int i=0 ;i<n;++i){ if (fabs (a[i][n]) < ZERO) a[i][n] = 0 ; printf ("%.2lf\n" , a[i][n]); } return 0 ; }
(2) 异或版
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 48 49 50 51 52 53 54 55 56 57 58 int n;const int N = 100 + 10 ;int a[N][N];int gauss () { int c = 0 , r = 0 ; for (c=0 ;c<n;++c){ int t = r; for (int i=r;i<n;++i){ if (a[i][c] == 1 ){ t = i;break ; } } if (a[t][c] == 0 ) continue ; for (int i=0 ;i<n+1 ;++i) swap (a[t][i], a[r][i]); for (int i=r+1 ;i<n;++i){ if (a[i][c] > 0 ){ for (int j=n;j>=c;--j){ a[i][j] ^= a[r][j]; } } } ++r; } if (r < n){ for (int i=r;i<n;++i){ if (a[i][n]) return 2 ; } return 1 ; } for (int i=n-1 ;i>=0 ;--i){ for (int j=i+1 ;j<n;++j){ a[i][n] ^= (a[i][j]*a[j][n]); } } return 0 ; } int main (void ) { scanf ("%d" , &n); for (int i=0 ;i<n;++i){ for (int j=0 ;j<n+1 ;++j){ scanf ("%d" , &a[i][j]); } } int t = gauss (); if (t == 1 ){ puts ("Multiple sets of solutions" ); return 0 ; } if (t == 2 ){ puts ("No solution" ); return 0 ; } for (int i=0 ;i<n;++i) printf ("%d\n" , a[i][n]); return 0 ; }
容斥原理
假如有一个Venn图,包含四个集合S 1 , S 2 , S 3 , S 4 S_1,S_2,S_3,S_4 S 1 , S 2 , S 3 , S 4 ,求S 1 ⋃ S 2 ⋃ S 3 ⋃ S 4 S_1\bigcup S_2\bigcup S_3\bigcup S_4 S 1 ⋃ S 2 ⋃ S 3 ⋃ S 4 中包含元素的个数,则:
\begin{align} & |S_1\bigcup S_2\bigcup S_3\bigcup S_4| \\ &=|S_1|+|S_2|+|S_3|+|S_4| \\ & -|S_1\bigcap S_2|-|S_1\bigcap S_3|-|S_1\bigcap S_4|-|S_2\bigcap S_3|-|S_2\bigcap S_4|-|S_3\bigcap S_4| \\ & +|S_1\bigcap S_2\bigcap S_3|+|S_1\bigcap S_2\bigcap S_4|+|S_2\bigcap S_3\bigcap S_4| \\ & -|S_1\bigcap S_2\bigcap S_3\bigcap S_4| \end{align}
那么对于n n n 个集合的并,每一项若为奇数个集合的交,则为正,若是偶数个集合的交则为负,然后相加。显然,这个式子一共有2 n − 1 2^n-1 2 n − 1 项。
设B = { S 1 , S 2 , . . . , S n } B=\{S_1,S_2,...,S_n\} B = { S 1 , S 2 , . . . , S n } ,则
∣ ⋃ i = 1 n S i ∣ = ∑ C ⊆ B ( − 1 ) ∣ C ∣ − 1 ∣ ⋂ e ∈ C e ∣ |\bigcup\limits^n_{i=1}S_i |=\sum\limits _{C\subseteq B}(-1)^{|C|-1}|\bigcap\limits _{e\in C}e| ∣ i = 1 ⋃ n S i ∣ = C ⊆ B ∑ ( − 1 ) ∣ C ∣ − 1 ∣ e ∈ C ⋂ e ∣
这个公式概率论也可以用哦!S i S_i S i 就表示i i i 事件发生的概率
时间复杂度:O ( 2 n ) O(2^n) O ( 2 n ) 。
严格证明:呃呃好累,来辆车创死我吧
要证明容斥定理是正确的,就等价于证明在集合S i S_i S i 中的任一元素,都在右边的等式中被计算了正好一次。
假设元素x x x 出现在了k k k 个S i S_i S i 集合中,k ∈ [ 1 , n ] k\in[1,n] k ∈ [ 1 , n ]
当∣ C ∣ = 1 |C|=1 ∣ C ∣ = 1 时,x x x 被加了k k k 次,当∣ C ∣ = 2 |C|=2 ∣ C ∣ = 2 时,x x x 被减了C k 2 C_k^2 C k 2 次;
……
当∣ C ∣ = k |C|=k ∣ C ∣ = k 时,x x x 被加了( − 1 ) k − 1 C k k (-1)^{k-1}C_k^k ( − 1 ) k − 1 C k k 次。
那么,元素x x x 总共被计算了T = C k 1 − C k 2 + . . . + ( − 1 ) i − 1 × C k i + . . . + ( − 1 ) k − 1 × C k k T=C_k^1-C_k^2+...+(-1)^{i-1}\times C_k^i+...+(-1)^{k-1}\times C_k^k T = C k 1 − C k 2 + . . . + ( − 1 ) i − 1 × C k i + . . . + ( − 1 ) k − 1 × C k k 次
由二项式定理,可知( 1 − a ) k = C k 0 × a 0 − C k 1 × a 1 + . . . + ( − 1 ) k × C k k × a k (1-a)^k=C_k^0\times a^0-C_k^1\times a^1+...+(-1)^k\times C_k^k\times a_k ( 1 − a ) k = C k 0 × a 0 − C k 1 × a 1 + . . . + ( − 1 ) k × C k k × a k ,
令a = 1 a=1 a = 1 ,则( 1 − 1 ) k = C k 0 × 1 0 − T (1-1)^k=C^0_k\times1^0-T ( 1 − 1 ) k = C k 0 × 1 0 − T ,即T = 1 − 0 = 1 T=1-0=1 T = 1 − 0 = 1 ,得证。
一个不算模板的模板,求1 1 1 ~n n n 之间能被p [ m ] p[m] p [ m ] 数组中任一元素整除的数的个数。代码:
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 int n, m;const int M = 16 + 5 ;int p[M];int main (void ) { scanf ("%d%d" , &n, &m); for (int i=0 ;i<m;++i) scanf ("%d" , &p[i]); int res = 0 ; for (int i=1 ;i<1 <<m;++i){ int t = 1 , s = 0 ; for (int j=0 ;j<m;++j){ if ((i>>j) & 1 ){ if ((ll)t * p[j] > n){ t = -1 ; break ; } t *= p[j]; ++s; } } if (t > 0 ){ if (s%2 ) res += n / t; else res -= n/t; } } printf ("%d\n" , res); return 0 ; }
常见数
俺觉得学常见数,更多的可以说是借着常见数来学习如何推公式,以及其中dp状态转移的化简,对子问题的划分xd
1.卡特兰数(Catalan Number)
捏麻麻地 卡特兰数 奥妙无穷
ps:这篇博客说的应用非常好,但是太多了,贴个链接
https://zhuanlan.zhihu.com/p/31317307
(1) 定义:
设卡特兰数的第n n n 项为h ( n ) h(n) h ( n ) ,h ( 0 ) = 1 , h ( 1 ) = 1 h(0)=1,h(1)=1 h ( 0 ) = 1 , h ( 1 ) = 1 。catalan数满足递推式:
h ( n ) = h ( 0 ) × h ( n ) + h ( 1 ) × h ( n − 1 ) × . . . × h ( n − 1 ) × h ( 0 ) h(n)=h(0)\times h(n)+h(1)\times h(n-1)\times ...\times h(n-1)\times h(0) h ( n ) = h ( 0 ) × h ( n ) + h ( 1 ) × h ( n − 1 ) × . . . × h ( n − 1 ) × h ( 0 ) ,n ≥ 2 n\geq 2 n ≥ 2
此时递推关系的解为h ( n ) = C 2 n n n + 1 h(n)=\frac{C_{2n}^n}{n+1} h ( n ) = n + 1 C 2 n n
另类递推式:h ( n ) = h ( n − 1 ) × 4 n − 2 n + 1 h(n)=h(n-1)\times \frac{4n-2}{n+1} h ( n ) = h ( n − 1 ) × n + 1 4 n − 2 ,或者说h ( n + 1 ) = h ( n ) × 4 n + 2 n + 2 h(n+1)=h(n)\times \frac{4n+2}{n+2} h ( n + 1 ) = h ( n ) × n + 2 4 n + 2
递推关系的另类解为h ( n ) = C 2 n n − C 2 n n − 1 h(n)=C_{2n}^n-C_{2n}^{n-1} h ( n ) = C 2 n n − C 2 n n − 1 。
事实上,它们都是等价的。(这些递推式还是挺容易证明的就不写了,敲公式好累)
以防一时看不出来,记一下前几项(误)(从第0项开始)
1, 1, 2, 5, 14, 42, 132, 429, 1430, 4862, 16796, 58786, 208012, 742900, 2674440, 9694845, 35357670, 129644790, 477638700, 1767263190, 6564120420, 24466267020, 91482563640, 343059613650, 1289904147324, 4861946401452
(2) 应用
题目见AcWing415,相当于问一个栈(无穷大)的进栈序列为1,2,3,…,n,有多少个不同的出栈序列。
对于每一个h ( k ) h(k) h ( k ) ,我们都可以把它的每个进出栈的过程看成独立的,然后枚举分割的方案,那么h ( k ) = h ( 0 ) × h ( k − 1 ) + . . . + h ( n − 1 ) × h ( 0 ) h(k)=h(0)\times h(k-1)+...+h(n-1)\times h(0) h ( k ) = h ( 0 ) × h ( k − 1 ) + . . . + h ( n − 1 ) × h ( 0 ) 。(我知道我讲的不好,但是意会一下QAQ)
或者这么想,对于每个数来说,必须进栈一次,出栈一次。把进栈设置为状态0,出栈设为1。那么,n n n 个数的所有状态对应n n n 个1和n n n 个0组成的长度为2 n 2n 2 n 的序列。显然,无论何时,进栈的数量都不小于出栈的数量,这个问题就转换成了AcWing889(我在刷题有详细写解法)
类似的还有买票找零问题,有2n个人排成一行进入剧场。入场费5元。其中只有n个人有一张5元钞票,另外n人只有10元钞票,剧院无其它钞票,问有多少种方法使得只要有10元的人买票,售票处就有5元的钞票找零(将持5元者到达视作将5元入栈,持10元者到达视作使栈中某5元出栈)。
在一个凸多边形中,通过若干条互不相交的对角线,把这个多边形划分成了若干个三角形。给定凸多边形的边数n n n ,求不同划分的方案数f ( n ) f(n) f ( n ) 。嗯……lcy讲过这个来着,but我还是贴一下百度百科的证明吧(捂脸)
因为凸多边形的任意一条边必定属于某一个三角形,所以我们以某一条边为基准,以这条边的两个顶点为起点P 1 P_1 P 1 和终点P n P_n P n ,将该凸多边形的顶点依序标记为P 1 , P 2 , . . . , P n P_1,P_2,...,P_n P 1 , P 2 , . . . , P n ,再在该凸多边形中找任意一个不属于这两个点的顶点P k , 2 ≤ k ≤ n − 1 P_k,2\leq k \leq n-1 P k , 2 ≤ k ≤ n − 1 ,来构成一个三角形,用这个三角形把一个凸多边形划分成两个凸多边形,其中一个凸多边形,是由P 1 , P 2 , . . . , P k P_1,P_2,...,P_k P 1 , P 2 , . . . , P k 构成的凸k k k 边形(顶点数即是边数),另一个凸多边形,是由P k , P k + 1 , . . . , P n P_k,P_{k+1},...,P_n P k , P k + 1 , . . . , P n 构成的凸n − k + 1 n-k+1 n − k + 1 边形。
此时,我们若把P k P_k P k 视为确定一点,那么根据乘法原理,f ( n ) f(n) f ( n ) 的问题就等价于——凸k k k 多边形的划分方案数乘以凸n − k + 1 n-k+1 n − k + 1 多边形的划分方案数,即选择Pk这个顶点的f ( n ) = f ( k ) × f ( n − k + 1 ) f(n)=f(k)×f(n-k+1) f ( n ) = f ( k ) × f ( n − k + 1 ) 。而k ∈ [ 2 , n − 1 ] k\in[2,n-1] k ∈ [ 2 , n − 1 ] ,所以再根据加法原理,将k取不同值的划分方案相加,得到的总方案数为:f ( n ) = f ( 2 ) f ( n − 2 + 1 ) + f ( 3 ) f ( n − 3 + 1 ) + … + f ( n − 1 ) f ( 2 ) f(n)=f(2)f(n-2+1)+f(3)f(n-3+1)+…+f(n-1)f(2) f ( n ) = f ( 2 ) f ( n − 2 + 1 ) + f ( 3 ) f ( n − 3 + 1 ) + … + f ( n − 1 ) f ( 2 ) 。也就是说,f ( n ) = h ( n − 2 ) f(n)=h(n-2) f ( n ) = h ( n − 2 ) 。
有n + 1 n+1 n + 1 个叶子节点的满位置二叉树(即每个节点有0或2个子节点,且左子节点和右子节点是不同的)的计数问题,相当于有n n n 个内节点的满位置二叉树的计数问题。
这道题,我还是和凸多边形划分相同的思路,可以发现,这些应用都有一个共同的特点,就是可以通过一次分割,划分为两个完全一致只是规模变小的子问题,并且,这两个子问题完全独立,那么所得方案数就可以通过乘法原理相乘,再枚举分割方法,根据加法原理把它们相加。
博客提供了一个很特别的思路。
长方形填充阶梯形状
n对括号正确匹配数目, Young表问题, 不相交的弦, 笔画群峰, 不出现312模式的全排列
这些都是很经典的问题,但是太多了写不下~~(不想写)~~,可以自己看我上面的链接哦,我觉得值得一看!特别是全排列和young表
2. 斐波那契数
(1) 定义:
递推式:F 0 = 0 , F 1 = 1 F_0=0,F_1=1 F 0 = 0 , F 1 = 1 ,F n = F n − 1 + F n − 2 F_n=F_{n-1}+F_{n-2} F n = F n − 1 + F n − 2 ,n ≥ 3 , n ∈ N n\geq3,n\in \N n ≥ 3 , n ∈ N 。
通项公式:F n = 1 5 [ ( 1 + 5 2 ) n + ( 1 − 5 2 ) n ] F_n=\frac{1}{\sqrt 5}[(\frac{1+\sqrt 5}{2})^n+(\frac{1-\sqrt 5}{2})^n] F n = 5 1 [ ( 2 1 + 5 ) n + ( 2 1 − 5 ) n ]
(通项公式就是用待定系数法搞成等比数列然后组合证明出来的,可以看https://www.cnblogs.com/1024th/p/10902775.html这个博客,这里简单说一下另一种方式)
显然可以构造等比数列C n = F n − a F n − 1 C_n=F_n-aF_{n-1} C n = F n − a F n − 1 ,那么F n − a F n − 1 = b ( F n − 1 − a F n − 2 ) F_n-aF_{n-1}=b(F_{n-1}-aF_{n-2}) F n − a F n − 1 = b ( F n − 1 − a F n − 2 ) ,代入递推式就可以解出来a , b a,b a , b 的值,得到C n = b n − 1 C_n=b^{n-1} C n = b n − 1 ,则F n = b n − 1 + a F n − 1 F_n=b^{n-1}+aF_{n-1} F n = b n − 1 + a F n − 1 ,这样就把二阶递推式化成了一阶递推式,然后再做一次待定系数法,F n + k b n = − a ( F n − 1 + k b n − 1 ) F_n+kb^n=-a(F_{n-1}+kb^{n-1}) F n + k b n = − a ( F n − 1 + k b n − 1 ) ,然后不停化简,得到k k k 的式子,再把a , b a,b a , b 两组解的任何一组代进去(两组解结果一样的),就得到了F n = 1 5 [ ( 1 + 5 2 ) n + ( 1 − 5 2 ) n ] F_n=\frac{1}{\sqrt 5}[(\frac{1+\sqrt 5}{2})^n+(\frac{1-\sqrt 5}{2})^n] F n = 5 1 [ ( 2 1 + 5 ) n + ( 2 1 − 5 ) n ] 。这个通项公式有叫“比内公式”,是无理数表示有理数的一个范例。
(2) 相关恒等式:
(证易,敲累,简写,博客有多处笔误,按俺的公式来)
∑ i = 1 n F i = F n + 2 − F 2 = F n + 2 − 1 \sum\limits _{i=1}^n F_i=F_{n+2}-F_2=F_{n+2}-1 i = 1 ∑ n F i = F n + 2 − F 2 = F n + 2 − 1
证明:F 1 + F 2 + F 3 + . . . = F 3 − F 2 + F 4 − F 3 + . . . = F n + 2 − F 2 F_1+F_2+F_3+...=F_3-F_2+F_4-F_3+...=F_{n+2}-F_2 F 1 + F 2 + F 3 + . . . = F 3 − F 2 + F 4 − F 3 + . . . = F n + 2 − F 2
F 1 2 + F 2 2 + ⋯ + F n 2 = F n F n + 1 F^2_1+F^2_2+⋯+F^2_n=F_nF_{n+1} F 1 2 + F 2 2 + ⋯ + F n 2 = F n F n + 1
证明:F n F n + 1 = F n ( F n − 1 + F n ) = F n 2 + F n − 1 ( F n − 2 + F n − 1 ) = . . . = ∑ i = 1 n F i 2 F_nF_{n+1}=F_n(F_{n-1}+F_n)=F_n^2+F_{n-1}(F_{n-2}+F_{n-1})=...=\sum\limits _{i=1}^n F_i^2 F n F n + 1 = F n ( F n − 1 + F n ) = F n 2 + F n − 1 ( F n − 2 + F n − 1 ) = . . . = i = 1 ∑ n F i 2
F 1 + F 3 + F 5 + . . . F 2 n − 1 = F 2 n F_1+F_3+F_5+...F_{2n-1}=F_{2n} F 1 + F 3 + F 5 + . . . F 2 n − 1 = F 2 n
证明:F 2 n = F 2 n − 1 + F 2 n − 2 = F 2 n − 1 + F 2 n − 3 + F 2 n − 4 + . . . = F 1 + F 3 + . . . + F 2 n − 1 F_{2n}=F_{2n-1}+F_{2n-2}=F_{2n-1}+F_{2n-3}+F_{2n-4}+...=F_1+F_3+...+F_{2n-1} F 2 n = F 2 n − 1 + F 2 n − 2 = F 2 n − 1 + F 2 n − 3 + F 2 n − 4 + . . . = F 1 + F 3 + . . . + F 2 n − 1
F 2 + F 4 + F 6 + . . . + F 2 n = F 2 n + 1 − F 1 = F 2 n + 1 − 1 F_2+F_4+F_6+...+F_{2n}=F_{2n+1}-F_1=F_{2n+1}-1 F 2 + F 4 + F 6 + . . . + F 2 n = F 2 n + 1 − F 1 = F 2 n + 1 − 1
证明:和上一个恒等式一模一样属于是
F n = F m F n − m + 1 + F m − 1 F n − m F_n=F_mF_{n-m+1}+F_{m-1}F_{n-m} F n = F m F n − m + 1 + F m − 1 F n − m
证明:数学归纳法
当m = 2 m=2 m = 2 时,F n = F 2 F n − 2 + 1 + F 2 − 1 F n − 2 = F n − 1 + F n − 2 F_n=F_2F_{n−2+1}+F_{2−1}F_{n−2}=F_{n−1}+F_{n−2} F n = F 2 F n − 2 + 1 + F 2 − 1 F n − 2 = F n − 1 + F n − 2 成立。
设当m = k ( 2 ≤ k ≤ n − 2 ) m=k(2≤k≤n−2) m = k ( 2 ≤ k ≤ n − 2 ) 时,F n = F k F n − k + 1 + F k − 1 F n − k F_n=F_kF_{n−k+1}+F_{k−1}F_{n−k} F n = F k F n − k + 1 + F k − 1 F n − k 成立。
$ \begin{align} & F_n=F_kF_{n−k+1}+(F_{k+1}−F_k)F_{n−k}\ &=F_{k+1}F_{n-k}+F_k(F_{n-k+1}-F_{n-k})\ & =F_{k+1}F_{n-k}+F_kF_{n-k-1} \end{align}$
即m = k + 1 m=k+1 m = k + 1 时等式依旧成立,得证。
F n − 1 F n + 1 = F n 2 + ( − 1 ) n F_{n-1}F_{n+1}=F_n^2+(-1)^n F n − 1 F n + 1 = F n 2 + ( − 1 ) n
数学归纳法,假设F n − 1 − 1 F n − 1 + 1 = F n − 1 2 + ( − 1 ) n − 1 F_{n-1-1}F_{n-1+1}=F_{n-1}^2+(-1)^{n-1} F n − 1 − 1 F n − 1 + 1 = F n − 1 2 + ( − 1 ) n − 1 成立
( F n − F n − 2 ) ( F n + F n − 1 ) = F n 2 + ( − 1 ) n (F_{n}-F_{n-2})(F_n+F_{n-1})=F_n^2+(-1)^n ( F n − F n − 2 ) ( F n + F n − 1 ) = F n 2 + ( − 1 ) n
⇔ F n 2 + F n F n − 1 − F n − 2 F n − F n − 2 F n − 1 = F n 2 + ( − 1 ) n \Leftrightarrow F_n^2+F_nF_{n-1}-F_{n-2}F_n-F_{n-2}F_{n-1}=F_n^2+(-1)^n ⇔ F n 2 + F n F n − 1 − F n − 2 F n − F n − 2 F n − 1 = F n 2 + ( − 1 ) n
⇔ F n − 1 ( F n − F n − 2 ) − F n − 2 F n = ( − 1 ) n \Leftrightarrow F_{n-1}(F_n-F_{n-2})-F_{n-2}F_{n}=(-1)^n ⇔ F n − 1 ( F n − F n − 2 ) − F n − 2 F n = ( − 1 ) n
⇔ − F n − 2 F n = − F n − 1 2 + ( − 1 ) n \Leftrightarrow -F_{n-2}F_n=-F_{n-1}^2+(-1)^n ⇔ − F n − 2 F n = − F n − 1 2 + ( − 1 ) n
⇔ F n − 1 − 1 F n − 1 + 1 = F n − 1 2 + ( − 1 ) n − 1 \Leftrightarrow F_{n-1-1}F_{n-1+1}=F_{n-1}^2+(-1)^{n-1} ⇔ F n − 1 − 1 F n − 1 + 1 = F n − 1 2 + ( − 1 ) n − 1 ,得证
(3) 性质:
证明:( F n , F n − 1 ) = ( F n − 1 , F n − F n − 1 ) (F_n,F_{n-1})=(F_{n-1},F_n-F_{n-1}) ( F n , F n − 1 ) = ( F n − 1 , F n − F n − 1 ) ,不断递归,( F 2 , F 1 ) = 1 (F_2,F_1)=1 ( F 2 , F 1 ) = 1 。
g c d ( F n , F m ) = F g c d ( n , m ) gcd(F_n,F_m)=F_{gcd(n,m)} g c d ( F n , F m ) = F g c d ( n , m )
证明:( F n , F m ) = ( F m F n − m + 1 + F m − 1 F n − m , F m ) , n > m (F_n,F_m)=(F_mF_{n-m+1}+F_{m-1}F_{n-m}, F_m), n>m ( F n , F m ) = ( F m F n − m + 1 + F m − 1 F n − m , F m ) , n > m
又( a , b ) = ( a − k b , b ) (a,b)=(a-kb,b) ( a , b ) = ( a − k b , b ) ,所以( F n , F m ) = ( F m − 1 F n − m , F m ) (F_n,F_m)=(F_{m-1}F_{n-m},F_m) ( F n , F m ) = ( F m − 1 F n − m , F m )
因为相邻项互质,所以F m − 1 , F m F_{m-1},F_m F m − 1 , F m 没有公因子,因此( F n , F m ) = ( F n − m , F m ) (F_n,F_m)=(F_{n-m},F_m) ( F n , F m ) = ( F n − m , F m )
不断递归下去,可得( F n , F m ) = ( F n m o d m , F m ) (F_n,F_m)=(F_{n\mod m},F_m) ( F n , F m ) = ( F n m o d m , F m )
交换m , n m o d m m,n \mod m m , n m o d m 的位置,一直做下去,我超 是你,欧几里得!得证。
n ∣ m , F n ∣ F m n|m,F_n|F_m n ∣ m , F n ∣ F m
相当于性质2的特例。
(4) 计算方法
参见yxc的求解斐波那契数列的若干方法 。
在此之外,补充另一种利用性质计算的方法:快速倍增法
(5) 应用
有些奇奇怪怪的组合数,化到最后是斐波那契和其他式子的组合,可以说是非常神奇(指我不会)记得cf有一题D?算灯塔方案,就用到了斐波那契数列
排列问题
康托展开
1. 不重复元素的康托展开
对于一个从1到n n n 的排列{ a 1 , a 2 , . . . , a n } \{a_1,a_2,...,a_n\} { a 1 , a 2 , . . . , a n } ,比它小的排列的数量为:∑ i = 1 n ( s i × ( n − i ) ! ) \sum\limits_{i=1}^n (s_i\times(n-i)!) i = 1 ∑ n ( s i × ( n − i ) ! )
其中,s i s_i s i 为在a i a_i a i 元素后面的所有元素中,小于a i a_i a i 的元素个数。
简单的说下证明:假设当前到了第i i i 位元素,那么前面i − 1 i-1 i − 1 位不变,把比a i a_i a i 小的元素拿到a i a_i a i 的位置,剩下的元素随便取,那么答案就是( n − i ) ! (n-i)! ( n − i ) ! 个,得证。有点数位dp的那个意思。namo 怎么查询比a i a_i a i 更小的元素呢?树状数组鸭wwwww
2. 可重复元素的康托展开
这个首先就要知道有重复元素的全排列个数的计算公式,n n n 个元素被分成k k k 类,每类中元素的值相同,且第i i i 类的元素个数为a k a_k a k 个,则这n n n 个元素的全排列数为n ! ∏ i = 1 k ( a k ! ) \frac{n!}{\prod_{i=1}^{k}(a_k!)} ∏ i = 1 k ( a k ! ) n ! 。
然后,剩下的思路和不重复元素的康托展开是一样的,数位dp+树状数组即可。
数据范围:n ≤ 50 n\leq 50 n ≤ 5 0 ;答案在long long范围内。
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 #include <cstdio> using namespace std;#define ri register int #define ll long long ll ans,c[55 ][55 ]; int a[10 ],len,n[55 ],cnt;ll multiqpl (int a[],int l) { ll res=1 ; for (ri i=0 ;i<=9 ;i++) { res*=c[l][a[i]]; l-=a[i]; } return res; } int main () { for (char ch=getchar ();ch>='0' &&ch<='9' ;ch=getchar ())a[n[++len]=ch-48 ]++; c[0 ][0 ]=1 ; for (ri i=1 ;i<=len;i++) { c[i][0 ]=c[i][i]=1 ; for (ri j=1 ;j<i;j++)c[i][j]=c[i-1 ][j]+c[i-1 ][j-1 ]; } for (ri i=1 ;i<=len;i++) { cnt=0 ; for (ri j=0 ;j<n[i];j++) if (a[j]) { a[j]--; ans+=multiqpl (a,len-i); a[j]++; } a[n[i]]--; } printf ("%lld" ,ans); }
树形结构
边权均为1的树的直径
任取一点作为起点,找到距离该点最远的一个点u u u (DFS or BFS)
再找到距离u u u 最远的一点v v v (DFS Or BFS)
那么u , v u,v u , v 之间的路径就是一条直径
**证明:**在无根树中,假设树的直径为b c bc b c ,以任意结点a a a 为根,得到距离a a a 最远的点为u u u 。
情况1:a u au a u 和b c bc b c 的交点为x x x 。
此时可以得到,∣ u x ∣ ≥ ∣ c x ∣ |ux| \geq |cx| ∣ u x ∣ ≥ ∣ c x ∣ ,因此,∣ b x ∣ + ∣ u x ∣ ≥ ∣ b x ∣ + ∣ c x ∣ |bx|+|ux| \geq |bx|+|cx| ∣ b x ∣ + ∣ u x ∣ ≥ ∣ b x ∣ + ∣ c x ∣ ,即∣ b u ∣ ≥ ∣ b c ∣ |bu|\geq |bc| ∣ b u ∣ ≥ ∣ b c ∣ ,因此,u u u 一定是直径的一个端点;
情况2:b c bc b c 和a u au a u 没有交点,那么一定存在路径x y xy x y ,使得x y xy x y 与a u , b c au,bc a u , b c 相交。由于u u u 是离a a a 最远的点,那么∣ u x ∣ ≥ ∣ x y ∣ + ∣ c y ∣ |ux| \geq |xy|+|cy| ∣ u x ∣ ≥ ∣ x y ∣ + ∣ c y ∣ ,因此∣ b y ∣ + ∣ x y ∣ + ∣ u x ∣ ≥ ∣ b y ∣ + ∣ c y ∣ |by|+|xy|+|ux|\geq |by|+|cy| ∣ b y ∣ + ∣ x y ∣ + ∣ u x ∣ ≥ ∣ b y ∣ + ∣ c y ∣ ,因此,u u u 一定是直径的一个端点。
边权不含负数
不含负数时,证明方法是和第一种情况同理的,此时可以用上面的方案来处理。
可以用两遍dfs(),或者使用树形DP。
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 int n;const int N = 1e4 + 10 ;int dis[N];struct node { int v;int w; }; vector<node> ve[N]; void dfs (int u, int pre) { for (node i : ve[u]){ if (i.v == pre) continue ; dis[i.v] = dis[u] + i.w; dfs (i.v, u); } } int main (void ) { ios::sync_with_stdio (false ); cin.tie (0 );cout.tie (0 ); cin >> n; int u, v, w, t, res; for (int i=1 ;i<n;++i){ cin >> u >> v >> w; ve[u].push_back (node{v, w}), ve[v].push_back (node{u, w}); } dis[1 ] = 0 ; dfs (1 , 0 ); t = -1 , res = -1 ; for (int i=1 ;i<=n;++i){ if (dis[i] > res){ res = dis[i], t = i; } } dis[t] = 0 ; dfs (t, 0 ); t = -1 , res = -1 ; for (int i=1 ;i<=n;++i){ if (dis[i] > res){ res = dis[i], t = i; } } printf ("%d\n" , res); return 0 ; }
边权任意(有正有负)
需要用到树形DP。因为在这种情况下,证明将失效。
比如,假如x y xy x y 的长度为负数,那么∣ u x ∣ ≥ ∣ x y ∣ + ∣ c y ∣ |ux| \geq |xy|+|cy| ∣ u x ∣ ≥ ∣ x y ∣ + ∣ c y ∣ 的条件,将无法推出∣ b y ∣ + ∣ x y ∣ + ∣ u x ∣ ≥ ∣ b y ∣ + ∣ c y ∣ |by|+|xy|+|ux|\geq |by|+|cy| ∣ b y ∣ + ∣ x y ∣ + ∣ u x ∣ ≥ ∣ b y ∣ + ∣ c y ∣ ,因为不能保证∣ u x ∣ ≥ ∣ c y ∣ − ∣ x y ∣ |ux|\geq |cy|-|xy| ∣ u x ∣ ≥ ∣ c y ∣ − ∣ x y ∣ 。
此时,将状态定义为:f [ i ] : f[i]: f [ i ] : 两条路径挂载的最高点为i i i 时,最长的两点间距离。方法1,是直接用最长距离d 1 d1 d 1 和次长距离d 2 d2 d 2 来更新答案。
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 int n, ans;const int N = 1e4 + 10 ;int f[N];struct node { int v;int w; }; vector<node> ve[N]; int dfs (int u, int pre) { int dis = 0 ; int d1 = 0 , d2 = 0 ; for (node i : ve[u]){ if (i.v == pre) continue ; int d = dfs (i.v, u) + i.w; dis = max (dis, d); if (d >= d1){ d2 = d1, d1 = d; } else if (d > d2) d2 = d; } ans = max (ans, d1 + d2); return dis; } int main (void ) { ios::sync_with_stdio (false ); cin.tie (0 );cout.tie (0 ); cin >> n; int u, v, w; for (int i=1 ;i<n;++i){ cin >> u >> v >> w; ve[u].push_back (node{v, w}), ve[v].push_back (node{u, w}); } dfs (1 , 0 ); printf ("%d\n" , ans); return 0 ; }
方法2,就是正统的搞个f [ n ] f[n] f [ n ] 来记录答案。(不过其实是一个意思)
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 int n, ans;const int N = 1e4 + 10 ;int f[N];struct node { int v;int w; }; vector<node> ve[N]; void dfs (int u, int pre) { for (node i : ve[u]){ if (i.v == pre) continue ; dfs (i.v, u); ans = max (ans, f[u] + f[i.v] + i.w); f[u] = max (f[u], f[i.v] + i.w); } } int main (void ) { ios::sync_with_stdio (false ); cin.tie (0 );cout.tie (0 ); cin >> n; int u, v, w; for (int i=1 ;i<n;++i){ cin >> u >> v >> w; ve[u].push_back (node{v, w}), ve[v].push_back (node{u, w}); } dfs (1 , 0 ); printf ("%d\n" , ans); return 0 ; }
求树的中心
(有正有负的情况)
最直白的想法,就是把每个点离它最远的点求出来,然后比较找到最小值,正解也确实是这样。那么对于结点i i i ,就有两种可能:一是向下走遇到最远点(此时最远点为i的孩子);
二是向上走(可能先向上走再向下走)遇到最远点(此时最远点不是它的孩子)。
因此,进行两次树形DP,先求出
d o w n [ i ] : down[i]: d o w n [ i ] : 以i i i 为两条路径的最高点,求最长路径和次长路径,以及最长路径所在的子节点;
再求出u p [ i ] : up[i]: u p [ i ] : 从i i i 开始向上走,离最远点的距离;
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 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 int n;const int N = 1e4 + 10 ;struct dist { int d1, d2, inx; }; dist down[N]; struct node { int v, w; }; vector<node> ve[N]; int up[N], f[N];int dfs (int cur, int pre) { int d1 = 0 , d2 = 0 , inx = 0 ; for (node i : ve[cur]){ if (i.v == pre) continue ; int d = dfs (i.v, cur) + i.w; if (d > d1){ inx = i.v, d2 = d1, d1 = d; } else if (d > d2){ d2 = d; } } down[cur] = dist{d1, d2, inx}; return d1; } void dp (int cur, int pre, int d) { int inx = down[pre].inx, d1 = down[pre].d1, d2 = down[pre].d2; up[cur] = up[pre] + d; if (inx == cur){ up[cur] = max (up[cur], d2 + d); } else { up[cur] = max (up[cur], d1 + d); } f[cur] = max (up[cur], down[cur].d1); for (node i : ve[cur]){ if (i.v == pre) continue ; dp (i.v, cur, i.w); } } int main (void ) { int u, v, w; ios::sync_with_stdio (false ); cin.tie (0 );cout.tie (0 ); cin >> n; for (int i=1 ;i<n;++i){ cin >> u >> v >> w; ve[u].push_back (node{v, w}), ve[v].push_back (node{u, w}); } dfs (1 , 0 ); dp (1 , 0 , 0 ); int res = 1e9 + 10 ; for (int i=1 ;i<=n;++i){ res = min (res, f[i]); } cout << res << endl; return 0 ; }
例题:组合数+树形DP+建虚树
E1 Rubik’s Cube Coloring(easy version)
给结点数为2 k − 1 2^k-1 2 k − 1 的完全二叉树染色,一共有6种颜色,限制为:
白色不能和白色、黄色相邻;
黄色不能和白色、黄色相邻;
绿色不能和蓝色、绿色相邻;
蓝色不能和蓝色、绿色相邻;
红色不能和橘色、红色相邻;
橘色不能和红色、橘色相邻;
问有多少种方式,可以给全部节点染色,答案对1 0 9 + 7 10^9+7 1 0 9 + 7 取余。
首先,k = 1 k=1 k = 1 ,只有一个结点,有6种方式染色;
当k = 2 k=2 k = 2 时,当1号点的颜色固定时,2,3号只能在被ban掉的2种颜色之外,选择一种颜色进行染色,因此答案为6 × 4 × 4 6\times 4\times 4 6 × 4 × 4 ;
当k = 3 k=3 k = 3 时,当2,3号的颜色固定时,它们的孩子有各16种组合方案,然后再看1号结点固定时,2,3只能在被ben掉的2种颜色之外,选择一种颜色,因此答案为6 × 4 × 4 × 1 6 2 6\times 4\times 4 \times 16^2 6 × 4 × 4 × 1 6 2 ;
因此,若结点数为2 k − 1 2^k-1 2 k − 1 ,则染色方案有6 × 1 6 2 k − 1 − 1 6\times 16^{2^{k-1}-1} 6 × 1 6 2 k − 1 − 1
也可以写成6 × 4 2 k − 2 6\times 4^{2^{k}-2} 6 × 4 2 k − 2
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 ll ans, p[63 ]; const ll MOD = 1e9 + 7 ;void init () { p[0 ] = 1 ; for (int i=1 ;i<63 ;++i){ p[i] = p[i-1 ] + p[i-1 ]; } } ll qmi (ll m, ll k, ll MOD) { ll res = 1LL %MOD, t = m; while (k){ if (k & 1 ) res = res * t % MOD; t = t*t % MOD; k>>=1 ; } return res; } int main (void ) { ll k; init (); cin >> k; ans = qmi (16 , p[k-1 ]-1 , MOD); ans = (ans*6 ) % MOD; cout << ans << endl; return 0 ; }
E2 Rubik’s Cube Coloring(hard version)
hard version的不同,就在于有些结点,事先指定了颜色,然后问方案数。完全二叉树共有k k k 层,指定n n n 个结点的颜色,对于接下来的n n n 行,每行都有一个v , s v,s v , s ,v v v 代表结点的编号,s s s 代表指定结点v v v 为s s s 颜色,求给完全二叉树染色的方案数,对1 0 9 + 7 10^9 + 7 1 0 9 + 7 取模。
白色不能和白色、黄色相邻;
黄色不能和白色、黄色相邻;
绿色不能和蓝色、绿色相邻;
蓝色不能和蓝色、绿色相邻;
红色不能和橘色、红色相邻;
橘色不能和红色、橘色相邻;
数据范围:1 ≤ k ≤ 60 , 1 ≤ n ≤ m i n ( 2 k − 1 , 2000 ) , 1 ≤ v ≤ 2 k − 1 1\leq k \leq 60,1\leq n \leq min(2^k-1,2000),1\leq v \leq 2^k-1 1 ≤ k ≤ 6 0 , 1 ≤ n ≤ m i n ( 2 k − 1 , 2 0 0 0 ) , 1 ≤ v ≤ 2 k − 1 。
咳咳,关键词,树 限制 方案数。这不就是一个树形DP嘛!
定义状态f [ i ] [ j ] f[i][j] f [ i ] [ j ] :结点i i i 染成颜色j j j 时,以i i i 为根的树的方案数量;
那么答案很显然就是:∑ j = 1 6 f [ 1 ] [ j ] \sum\limits _{j=1} ^6 f[1][j] j = 1 ∑ 6 f [ 1 ] [ j ] 。
准备开写的时候,一个严峻的问题:最多有2 60 − 1 2^{60}-1 2 6 0 − 1 个结点……那么怎么样能够利用这题的特性做简化呢?
可以看出,1结点被染色之后,它下面的孩子们,就都只剩下4种选法了。对于2,3而言,red, orange显然就被ban了。而对于4,5来说,如果2为剩下四种颜色中的一种,比如2为green,那么对于4,5,green,blue就被ban了。就本例子来说,1下面的孩子们,就共有4 4 4^4 4 4 种选法,4为1的孩子的数量。
这样,一个重要的简化就做好了:我们并不需要对整个树进行DP,只需要在这样的树上去做DP:以所有的指定的n n n 个结点为叶子,向上找到1(根节点)的树。
比如这样的一颗完全二叉树,我们只需要对1,2,3,5,6,7,10,13这几个结点进行DP就可以了。
解决了数据范围的问题,我们再来看看怎么写转移方程,这个就比较容易了。
假如当前的结点i i i 没有被指定颜色,那么f [ i ] [ j ] = ∑ k = 1 4 f [ 2 ∗ i ] [ c ] ∗ ∑ k = 1 4 f [ 2 ∗ i + 1 ] [ c ] f[i][j] = \sum_{k=1}^4 f[2*i][c]*\sum_{k=1}^4f[2*i+1][c] f [ i ] [ j ] = ∑ k = 1 4 f [ 2 ∗ i ] [ c ] ∗ ∑ k = 1 4 f [ 2 ∗ i + 1 ] [ c ] ;
如果i i i 被指定了颜色,那么只有与指定颜色相同的j j j 合法,f [ i ] [ j ] = 1 f[i][j]=1 f [ i ] [ j ] = 1 ,其余的f [ i ] [ j ] = 0 f[i][j]=0 f [ i ] [ j ] = 0 。
设做DP的这些结点的总数为w w w ,得到了r e s = ∑ j = 1 6 f [ 1 ] [ j ] res = \sum_{j=1}^6f[1][j] r e s = ∑ j = 1 6 f [ 1 ] [ j ] ,之后让r e s = r e s × 4 2 k − 1 − w res = res \times 4^{2^k-1-w} r e s = r e s × 4 2 k − 1 − w ,就是答案。
懒人直接进行一个正义的map。
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 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 {% raw %} #define ll long long typedef pair<ll,ll> pll;#define xx first #define yy second #define endl "\n" int k, n;const ll MOD = 1e9 + 7 ;map<string,int > c = {{"white" ,1 }, {"yellow" ,2 }, {"green" ,3 }, {"blue" ,4 }, {"red" ,5 }, {"orange" ,6 }}; vector<int > edge[7 ]; map<pll, ll> f; map<ll, int > def; set<ll> ms; ll p[64 ]; ll qmi (ll m, ll k, ll MOD) { ll res = 1LL %MOD, t = m; while (k){ if (k & 1 ) res = res * t % MOD; t = t * t % MOD; k >>= 1 ; } return res; } void init () { edge[1 ] = {3 , 4 , 5 , 6 }; edge[2 ] = {3 , 4 , 5 , 6 }; edge[3 ] = {1 , 2 , 5 , 6 }; edge[4 ] = {1 , 2 , 5 , 6 }; edge[5 ] = {1 , 2 , 3 , 4 }; edge[6 ] = {1 , 2 , 3 , 4 }; p[0 ] = 1 ; for (int i=1 ;i<64 ;++i){ p[i] = p[i-1 ] + p[i-1 ]; } } ll dfs (ll cur, int color) { if (f.count (pll (cur, color))){ return f[pll (cur, color)]; } ll a1 = 0 , a2 = 0 ; ll ans = 1 ; if (def.count (cur)){ if (color!=def[cur]) return 0 ; } if (ms.count (cur+cur)){ for (auto i : edge[color]){ a1 = a1 + dfs (cur+cur, i) % MOD; } ans = a1 % MOD; } if (ms.count (cur+cur+1 )){ for (auto i : edge[color]){ a2 = a2 + dfs (cur+cur+1 , i) % MOD; } ans = (ans * a2) % MOD; } return f[pll (cur,color)] = ans; } int main (void ) { ios::sync_with_stdio (false ); cin.tie (0 );cout.tie (0 ); ll id; string color; init (); cin >> k >> n; for (int i=1 ;i<=n;++i){ cin >> id >> color; def[id] = c[color]; } for (map<ll,int >::iterator iter = def.begin ();iter!=def.end ();iter++){ id = iter->xx; while (id!=0 ){ ms.insert (id); id /= 2 ; } } ll ans = 0 ; for (int i=1 ;i<7 ;++i){ ans = (ans + dfs (1 , i)) % MOD; } ll exp = (p[k] - 1 - ms.size ()); ll mix = qmi (4 , exp, MOD); ll res = mix * ans % MOD; cout << res << endl; return 0 ; } {% endraw %}
例题:树的直径+单调栈/二分
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 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 int n, s, len;const int N = 3e5 + 5 ;struct node { int id, w; }; vector<node> ve[N]; vector<int > line; int fa[N], w[N], dis[N], a[N], tr[N<<1 ];bool st[N];void dfs (int u, int pre) { fa[u] = pre; for (auto to : ve[u]){ if (to.id == pre) continue ; dis[to.id] = dis[u] + to.w; dfs (to.id, u); } } void dfs2 (int u, int pre, int id) { for (auto to : ve[u]){ if (st[to.id] || to.id == pre) continue ; w[id] = max (w[id], dis[to.id] - dis[id]); dfs2 (to.id, u, id); } } int lowbit (int x) { return x & (-x); } void update (int x) { while (x <= len){ tr[x] = w[line[x]]; for (int i=1 ;i<lowbit (x);i<<=1 ){ tr[x] = max (tr[x], tr[x - i]); } x += lowbit (x); } } int query (int x, int y) { int res = 0 ; while (y >= x){ res = max (res, w[line[y]]); y--; for (;y-lowbit (y)>=x;y-=lowbit (y)){ res = max (res, tr[y]); } } return res; } void solve () { n = read (), s = read (); for (int i=1 ;i<n;++i){ int u, v, w; u = read (), v = read (), w = read (); ve[u].push_back (node{v, w}), ve[v].push_back (node{u, w}); } if (n == 1 ){ puts ("0" ); return ; } int rt = 1 , mx, ed, id; dfs (1 , 0 ); rt = max_element (dis + 1 , dis + n + 1 ) - dis; dis[rt] = 0 ; dfs (rt, 0 ); ed = max_element (dis + 1 , dis + n + 1 ) - dis; id = ed, mx = dis[id]; line.push_back (0 ); while (id){ line.push_back (id), st[id] = true ; id = fa[id]; } len = line.size () - 1 ; for (int i=1 ;i<=len;++i){ dfs2 (line[i], 0 , line[i]); } for (int i=0 ;i<len;++i){ update (i + 1 ); } int res = 1e9 + 7 ; for (int i=1 ;i<=len;++i){ int L = i, R = len, mid, ans = -1 ; while (L <= R){ mid = (L + R) >> 1 ; if (dis[line[i]] - dis[line[mid]] <= s){ ans = mid; L = mid + 1 ; } else R = mid - 1 ; } int cur = max (dis[line[ans]], dis[ed] - dis[line[i]]); cur = max (cur, query (i, ans)); res = min (res, cur); } printf ("%d\n" , res); } int main (void ) { int T; T = 1 ; while (T--){ solve (); } return 0 ; }
礼貌性贴一下单调队列的O ( N ) O(N) O ( N ) 代码
排序
归并排序
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 #include <iostream> using namespace std;#define ll long long int n;const int Max_n = 1e6 + 10 ;int a[Max_n], tmp[Max_n];void merge_sort (int a[], int le, int ri) { if (le >= ri) return ; int mid = (le+ri)>>1 ; merge_sort (a, le, mid); merge_sort (a, mid+1 , ri); int cnt=0 , i=le, j=mid+1 ; while (i<=mid && j<=ri){ if (a[i] <= a[j]) tmp[++cnt] = a[i++]; else tmp[++cnt] = a[j++]; } while (i<=mid) tmp[++cnt] = a[i++]; while (j<=ri) tmp[++cnt] = a[j++]; for (int i=1 ;i<=cnt;++i){ a[i+le-1 ] = tmp[i]; } } int main (void ) { n = read (); for (int i=1 ;i<=n;++i){ a[i] = read (); } merge_sort (a, 1 , n); for (int i=1 ;i<=n;++i){ printf (i==n?"%d\n" :"%d " , a[i]); } return 0 ; }
快速排序
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 #include <bits/stdc++.h> using namespace std;#define ll long long int n;const int Max_n = 1e6 + 10 ;int a[Max_n];void quick_sort (int a[], int le, int ri) { if (le >= ri) return ; int i = le-1 , j = ri+1 , x = a[(le+ri)>>1 ]; while (i < j){ do i++; while (a[i] < x); do j--; while (a[j] > x); if (i < j){ swap (a[i], a[j]); } } quick_sort (a, le, j); quick_sort (a, j+1 , ri); } int main (void ) { n = read (); for (int i=1 ;i<=n;++i){ a[i] = read (); } quick_sort (a, 1 , n); for (int i=1 ;i<=n;++i){ printf (i==n?"%d\n" :"%d " , a[i]); } return 0 ; }
快速选择(基于快排)
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 48 #include <bits/stdc++.h> using namespace std;#define ll long long int n, k;const int Max_n = 1e6 + 10 ;int a[Max_n];int quick_sort (int le, int ri, int k) { if (le == ri) return a[le]; int x = a[le], i = le-1 , j = ri+1 ; while (i < j){ while (a[++i] < x); while (a[--j] > x); if (i < j){ swap (a[i], a[j]); } } int sle = j-le+1 ; if (k <= sle){ return quick_sort (le, j, k); } return quick_sort (j+1 , ri, k-sle); } int main (void ) { n = read (), k = read (); for (int i=1 ;i<=n;++i){ a[i] = read (); } int ans = quick_sort (1 , n, k); printf ("%d\n" , ans); }
求逆序对(基于归并排序)
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 #include <iostream> using namespace std;#define ll long long int n;const int Max_n = 1e6 + 10 ;int a[Max_n], tmp[Max_n];ll merge_sort (int a[], int le, int ri) { if (le >= ri) return 0 ; int mid = (le+ri)>>1 ; ll res = merge_sort (a, le, mid) + merge_sort (a, mid+1 , ri); int cnt = 0 , i = le, j = mid + 1 ; while (i<=mid && j<=ri){ if (a[i] <= a[j]) tmp[++cnt] = a[i++]; else { tmp[++cnt] = a[j++]; res += (mid - i + 1 ); } } while (i <= mid) tmp[++cnt] = a[i++]; while (j <= ri) tmp[++cnt] = a[j++]; for (int i=1 ;i<=cnt;++i){ a[i+le-1 ] = tmp[i]; } return res; } int main (void ) { n = read (); for (int i=1 ;i<=n;++i){ a[i] = read (); } ll res = merge_sort (a, 1 , n); printf ("%lld\n" , res); return 0 ; }
博弈论
公平组合游戏ICG
有向图游戏
Nim游戏
如果不等于0,我们一定可以把它变为A 1 ⊕ A 2 . . . A n = 0 A_1 \oplus A_2...A_n=0 A 1 ⊕ A 2 . . . A n = 0 ,直至A 1 = A 2 . . . = A n = 0 A_1 = A_2...=A_n=0 A 1 = A 2 . . . = A n = 0 ,即拿光,对手无法再操作。证明:
假设a 1 ⊕ a 2 . . . a n = x ≠ 0 a_1\oplus a_2...a_n=x\neq 0 a 1 ⊕ a 2 . . . a n = x = 0 ,x x x 的二进制表示中最高位的1在第k k k 位,则必然至少存在一个a i a_i a i ,a i a_i a i 的第k k k 位二进制位为1,且a i ⊕ x < a i a_i\oplus x < a_i a i ⊕ x < a i 。从a i a_i a i 中拿走a i − ( a i ⊕ x ) a_i-(a_i\oplus x) a i − ( a i ⊕ x ) 这么多石子,也就相当于把a i a_i a i 变成a i ⊕ x a_i \oplus x a i ⊕ x 。那么,剩下的所有石子的异或就变成了a 1 ⊕ a 2 . ⊕ a i ⊕ x ⊕ . . a n = x ⊕ x = 0 a_1\oplus a_2.\oplus a_i \oplus x \oplus..a_n=x\oplus x = 0 a 1 ⊕ a 2 . ⊕ a i ⊕ x ⊕ . . a n = x ⊕ x = 0 。结论成立。
SG函数
Mex运算:设S表示一个非负数整数集合,定义mex(S)为求出不属于集合S的最小非负整数的运算。
在有向图游戏中,对于每个结点x x x ,设从x x x 出发共有k k k 条有向边,分别到达结点y 1 , y 2 , . . . , y k y_1,y_2,...,y_k y 1 , y 2 , . . . , y k ,定义SG(x)的后继结点y 1 , y 2 , . . . , y k y_1,y_2,...,y_k y 1 , y 2 , . . . , y k 的SG函数值构成的集合再执行mex(S)运算的结果,即:
S G ( x ) = m e x ( S G ( y 1 ) , S G ( y 2 ) , . . . , S G ( y k ) ) SG(x)=mex(SG(y_1), SG(y_2),...,SG(y_k)) S G ( x ) = m e x ( S G ( y 1 ) , S G ( y 2 ) , . . . , S G ( y k ) )
SG(终点)=0,SG(x)=x一步不能到达的最小非负整数。
特别的,整个有向图游戏G的SG函数值被定义为有向图起点s s s 的SG函数值,即S G ( G ) = S G ( s ) SG(G)=SG(s) S G ( G ) = S G ( s )
若只有一个游戏,先手面对x x x ,S G ( x ) = 0 SG(x)=0 S G ( x ) = 0 ,必输;S G = ( x ) ≠ 0 SG=(x)\neq 0 S G = ( x ) = 0 ,必赢。
例题:AcWing893
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 int n, m;const int Max_n = 10000 + 10 ;int op[Max_n], num[Max_n], f[Max_n];int sg (int x) { if (f[x] != -1 ) return f[x]; unordered_set<int > us; for (int i=1 ;i<=m;++i){ if (x >= op[i]) us.insert (sg (x-op[i])); } for (int i=0 ;;++i){ if (us.count (i)==0 ){ return f[x] = i; } } return -1 ; } void solve () { m = read (); for (int i=1 ;i<=m;++i){ op[i] = read (); } n = read (); for (int i=1 ;i<=n;++i){ num[i] = read (); } memset (f,-1 ,sizeof (f)); f[0 ] = 0 ; int res = 0 ; for (int i=1 ;i<=n;++i){ int x = num[i]; res = res ^ sg (x); } if (res!=0 ) puts ("Yes" ); else puts ("No" ); }
有向图游戏的和
设G 1 , G 2 , . . . , G m G_1,G_2,...,G_m G 1 , G 2 , . . . , G m 是m m m 个有向图游戏,定义有向图游戏G G G ,它的行动规则是任选某个有向图G i G_i G i ,并在G i G_i G i 上行动一步,G G G 被称为有向图游戏G 1 , G 2 , . . . , G m G_1,G_2,...,G_m G 1 , G 2 , . . . , G m 的并(和)。
有向图游戏的和的SG函数等于它包含的各个子游戏SG函数值的异或和,即
S G ( G ) = S G ( G 1 ) ⊕ S G ( G 2 ) ⊕ . . . ⊕ S G ( G m ) SG(G)=SG(G_1)\oplus SG(G_2)\oplus ...\oplus SG(G_m) S G ( G ) = S G ( G 1 ) ⊕ S G ( G 2 ) ⊕ . . . ⊕ S G ( G m )
证明方法和Nim游戏证明相同。
定理:
有向图游戏的某个局面必胜,当且仅当该局面对应节点的SG函数值大于0。
有向图游戏的某个局面必败,当且仅当该局面对应节点的SG函数值等于0。
位运算小知识
概率与数学期望
公式1: E ( a X + b Y ) = a × E ( X ) + b × E ( Y ) E(aX+bY)=a\times E(X)+b\times E(Y) E ( a X + b Y ) = a × E ( X ) + b × E ( Y )
奇奇怪怪小技巧
1. 拆数
888..888 = 8 × 111..111 = 8 × 999..999 9 = 8 × 1 0 x − 1 9 888..888=8\times 111..111=8\times \frac{999..999}{9}=8\times \frac{10^x-1}{9} 8 8 8 . . 8 8 8 = 8 × 1 1 1 . . 1 1 1 = 8 × 9 9 9 9 . . 9 9 9 = 8 × 9 1 0 x − 1
2334..899 = 111..111 + 1111 + 1111 + . . . 2334..899=111..111+1111+1111+... 2 3 3 4 . . 8 9 9 = 1 1 1 . . 1 1 1 + 1 1 1 1 + 1 1 1 1 + . . .
刷题
组合数 1622D Shuffle
给定一个01串s [ n ] s[n] s [ n ] ,最多可以进行一次如下操作:选择一个包含精确的k k k 个1的连续子串,然后将里面的元素任意排列。计算s s s 最多可以获得多少种01串,答案模998244353。
数据范围:2 ≤ n ≤ 5000 ; 0 ≤ k ≤ n 2\leq n\leq 5000;0\leq k\leq n 2 ≤ n ≤ 5 0 0 0 ; 0 ≤ k ≤ n 。
嗯……首先,要不重不漏的枚举所有的方案,那么就可以枚举所有的i , j i,j i , j :在操作中第一个被改变和最后一个被改变的字符下标。然后判断它们是否可以属于同一个连续子串,如果可以的话,令c为( i , j ) (i,j) ( i , j ) 区间的长度,c 1 c1 c 1 为[ i , j ] [i,j] [ i , j ] 间中能自由移动的1的个数,那么改变[ i , j ] [i,j] [ i , j ] 区间,其他部分保持不变的方案数就是C ( c , c 1 ) C(c,c1) C ( c , c 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 int n, m;const int N = 5000 + 5 , M = 998244353 ;int C[N][N], f[N], pre[N];char s[N];void init () { for (int i=0 ;i<N;++i){ for (int j=0 ;j<=i;++j){ if (!j) C[i][j] = 1 ; else C[i][j] = (C[i-1 ][j-1 ] + C[i-1 ][j]) % M; } } for (int i=1 ;i<=n;++i) pre[i] = pre[i-1 ] + (s[i]=='1' ); } int main (void ) { scanf ("%d%d" , &n, &m); scanf ("%s" , s+1 ); init (); if (m == 0 || pre[n] < m){ puts ("1" ); return 0 ; } ll ans = 1LL % M; for (int i=1 ;i<=n;++i){ for (int j=i+1 ;j<=n;++j){ int c = j - i + 1 - 2 , c1 = pre[j] - pre[i - 1 ]; if (c1 > m) continue ; if (s[i] == '0' ) --c1; if (s[j] == '0' ) --c1; if (c>=c1 && c>=0 && c1>=0 ){ ans = (ans + C[c][c1]) % M; } } } printf ("%lld\n" , ans); return 0 ; }
暴力枚举贡献1622E Math Test
这题就要想到,绝对值只有两种形式,一种是x i − r i x_i-r_i x i − r i ,一种是r i − x i r_i-x_i r i − x i ,看到n ≤ 10 n\leq 10 n ≤ 1 0 ,因此直接枚举正负号情况,然后寻找其中的最大值就好啦。
那么这个surprise value就可以写成∑ i = 1 n ∣ x i − r i ∣ = ∑ i = 1 n c × ( x i − r i ) , c ∈ { − 1 , 1 } \sum\limits _{i=1}^n |x_i-r_i|=\sum\limits _{i=1}^n c\times (x_i-r_i),c\in\{-1,1\} i = 1 ∑ n ∣ x i − r i ∣ = i = 1 ∑ n c × ( x i − r i ) , c ∈ { − 1 , 1 } 。x i x_i x i 是已知的,因此很容易就可以根据枚举的情况算出∑ i = 1 n c × x i \sum\limits _{i=1}^nc\times x_i i = 1 ∑ n c × x i 。那么怎么算学生的真实得分呢?首先啊,p [ m ] p[m] p [ m ] 是一个排列,也就是每个问题都有一个1~m的权重,显然,如果我们要让∑ j = 1 n p j v j \sum\limits _{j=1}^np_jv_j j = 1 ∑ n p j v j 尽可能大,假设第j j j 个问题对惊奇值的贡献系数是v j v_j v j ,那就要让更大的贡献系数搭配更大的问题权重。因此,就对问题按照贡献系数的大小进行排序,就能算出结果啦。
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 48 int n, m;const int N = 10 + 3 , M = 1e4 + 5 ;int g[N], p[M], a[M], best[M];char s[N][M]; bool cmp (int x, int y) { return a[x] < a[y]; } void solve () { cin >> n >> m; for (int i=1 ;i<=n;++i) cin >> g[i]; for (int i=1 ;i<=n;++i){ for (int j=1 ;j<=m;++j){ cin >> s[i][j]; } } for (int i=1 ;i<=m;++i) p[i] = i; int res = -1 ; for (int x=0 ;x<(1 <<n);++x){ for (int i=0 ;i<=m;++i) a[i] = 0 ; int half = 0 ; for (int i=1 ;i<=n;++i){ int c = (((x>>(i-1 ))&1 )?(1 ):(-1 )); half -= c * g[i]; for (int j=1 ;j<=m;++j){ if (s[i][j] == '1' ){ a[j] += c; } } } sort (p+1 , p+1 +m, cmp); int cur = half; for (int i=1 ;i<=m;++i){ cur += a[p[i]] * i; } if (cur > res){ res = cur; for (int i=1 ;i<=m;++i) best[p[i]] = i; } } for (int i=1 ;i<=m;++i){ cout << best[i] << (i==m?"\n" :" " ); } }
博弈论1537D Deleting Divisors
Alice和Bob又双叒在玩游戏,对于一个数字n,他们不断减去n的除了1和n之外的因子数,生成新的n。第一个无法进行操作的人就输了,Alice先手,问谁会赢。
c a s e 1 : case1: c a s e 1 : 如果n为奇数,那么它肯定没有偶数因子,Alice必定只能减掉一个奇数因子D D D ,减掉之后n就变成了一个偶数。并且,由于( n − D ) % D = 0 (n-D)\%D=0 ( n − D ) % D = 0 ,所以n − D ≠ 2 k n-D \neq 2^k n − D = 2 k 。
c a s e 2 : case2: c a s e 2 : 如果n为偶数并且n ≠ 2 k n \neq 2^k n = 2 k ,那么就可以分解成n = D o d d ∗ D e v e n n=D_{odd}*D_{even} n = D o d d ∗ D e v e n ,那么就可以减去这个奇数因子D D D ,偶数减奇数为奇数n − D n-D n − D 。这样做一定是最优的:因为,如果n − D n-D n − D 是质数,那么对手直接输了。如果不是,那么对手只能给我们一个新的n n n ,使得n ≠ 2 k n \neq 2^k n = 2 k ,我们又可以进行重复操作,直到对手拿到质数输掉比赛。为什么新的n ≠ 2 k n \neq 2^k n = 2 k 呢?因为假如n n n 可以被分解成n = 3 ∗ D n=3*D n = 3 ∗ D 的形式,那么3 3 3 的影响一直不会消失,直到D = 0 D=0 D = 0 ,此时n = 0 n=0 n = 0 。
因此,如果n = o d d n=odd n = o d d ,Bob赢,如果n = e v e n A n d n ≤ 2 k n=even~And~n \leq 2^k n = e v e n A n d n ≤ 2 k ,Alice赢。
c a s e 3 : case3: c a s e 3 : 如果n = 2 k n=2^k n = 2 k ,那么只可能产生两种情况:1) 把n减半,此时n = 2 k − 1 n=2^{k-1} n = 2 k − 1 ,要么,把n n n 变成一个不是2的幂的偶数。如果丢给对手n % 2 = 0 A n d n = 2 k n\%2=0~And~n=2^k n % 2 = 0 A n d n = 2 k ,在c a s e 2 case2 c a s e 2 中我们可以发现自己就必输,所以双方只能不停的把n n n 减半,直到有一方不能再减,那一方就输了。
因此,如果n = 2 k n=2^k n = 2 k ,如果k = o d d k=odd k = o d d ,Bob赢,否则,Alice赢。
组合数+思维 1569C Jury Meeting
首先把a [ n ] a[n] a [ n ] 数组排序,如果最大值a n a_n a n 的数量> 1 >1 > 1 ,那么所有排列都是n i c e nice n i c e 。
下面讨论a [ n ] a[n] a [ n ] 的数量为1时的情况。n i c e nice n i c e 的排列数量= = = 能构成的排列数量− - − 坏的排列的数量。在坏的排列中,所有的小于a [ n ] − 1 a[n]-1 a [ n ] − 1 的元素都要排在≥ a [ n ] − 1 \geq a[n]-1 ≥ a [ n ] − 1 的元素之前。也就是A n n − k − 1 = n ! ( k + 1 ) ! A^{n-k-1}_n=\frac{n!}{(k+1)!} A n n − k − 1 = ( k + 1 ) ! n ! 。设等于a [ n ] − 1 a[n]-1 a [ n ] − 1 的元素数量为k k k ,保证a [ n ] a[n] a [ n ] 在最后一个位置,剩下k k k 个数有k ! k! k ! 种排列方式,因此坏的排列的数量为n ! k ! ( k + 1 ) ! = n ! k + 1 \frac{n!k!}{(k+1)!}=\frac{n!}{k+1} ( k + 1 ) ! n ! k ! = k + 1 n ! 。
n i c e nice n i c e 的排列数量= n ! − n ! k + 1 =n!-\frac{n!}{k+1} = n ! − k + 1 n ! 。
AC代码:
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 int n;const int Max_n = 2e5 + 10 ;const ll MOD = 998244353 ;ll a[Max_n]; void solve () { n = read (); ll maxa = 0 ; for (int i=1 ;i<=n;++i){ a[i] = read (); if (a[i] > maxa) maxa = a[i]; } sort (a+1 , a+1 +n); ll num = count (a+1 , a+1 +n, a[n]-1 ) + 1 ; ll sub = 1 , ans = 1 ; for (ll i=1 ;i<=n;++i){ ans = ans * i % MOD; if (i != num) sub = sub * i % MOD; } if (count (a+1 ,a+1 +n, maxa) > 1 ){ printf ("%lld\n" , ans); return ; } ans = (ans - sub + MOD) % MOD; printf ("%lld\n" , ans); }
DP 1581C Portal
给定一个矩阵,每次操作可以将矩阵中的0变为1,或者将1变为0,求将矩阵的连续子矩阵变为如下形式(最少5行4列)需要的最小操作数:
上下左右都是1,中间都是0,四个角上的元素任意。很容易想到用二维前缀和,然后枚举。嗯……但是没想到怎么枚举,才能把复杂度降到O ( n 2 m ) O(n^2m) O ( n 2 m ) 。
这题是这样的,枚举子矩阵的第一行,最后一行的位置,然后枚举子矩阵第一列的位置,从最后的可能开始枚举,然后更新右半边,看右半边是加之前的结果比较好,还是直接加最后新的一列比较好,也就是一种利用前面计算结果的一种方式,把复杂度从O ( n 2 m 2 ) O(n^2m^2) O ( n 2 m 2 ) 降到O ( n 2 m ) O(n^2m) O ( n 2 m ) 的一种方式。我记得有一题也是差不多,翻转数组子序列的左右,用半径来简化的,这种都是巧妙利用之前结论的方式(不知道这种应该叫啥)
b,mina如图所示
AC代码如下:
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 int n, m;const int Max_n = 400 + 10 ;const int INF = 1e9 + 10 ;int pre[Max_n][Max_n], a[Max_n][Max_n];int rect (int a1, int b1, int a2, int b2) { return pre[a2][b2] - pre[a1-1 ][b2] - pre[a2][b1-1 ] + pre[a1-1 ][b1-1 ]; } void solve () { cin >> n >> m; char ch; for (int i=1 ;i<=n;++i){ for (int j=1 ;j<=m;++j){ cin >> ch; a[i][j] = ch - '0' ; pre[i][j] = pre[i-1 ][j] + pre[i][j-1 ] - pre[i-1 ][j-1 ] + a[i][j]; } } int ans = INF; for (int i=1 ;i<=n;++i){ for (int j=i+4 ;j<=n;++j){ int mina = INF, h = j-i+1 ; for (int x=m-3 ;x>=1 ;--x){ int b1 = h - 2 - rect (i+1 , x, j-1 , x); int b2 = 2 - rect (i, x+1 , i, x+2 ); int b3 = 2 - rect (j, x+1 , j, x+2 ); int b4 = rect (i+1 , x+1 , j-1 , x+2 ); int b = b1 + b2 + b3 + b4; mina = min (mina + !a[i][x+3 ] + !a[j][x+3 ] + rect (i+1 , x+3 , j-1 , x+3 ), h - 2 - rect (i+1 , x+3 , j-1 , x+3 )); ans = min (ans, b+mina); } } } printf ("%d\n" , ans); }
思维+统计贡献 1526D Kill Anton
https://codeforces.com/contest/1526/problem/D
一个字符串s s s 仅由ANOT四个字符构成,要求构造出一个s s s 的排列t t t ,使得t t t 经过最多的操作次数,才能变回s s s 。对一次操作的定义为:交换任意相邻的两个字符s i s_i s i 和s i + 1 s_{i+1} s i + 1 。
我自己的思路:
首先,我们可以想到,最终的答案,相同的字符应该是连在一起出现的。因为如果是分开的话,可能会变成这样:
本来是想把开头的A调换到最后一位的,但是由于采取最优策略,把离N最近的A调换到了最后面。除非变成这样,使得所有的A调换到后面都要花费很多距离,也就是所有的A都挨在一起。当然这只是我做题时候感性的想法。那么一共只有4个字符,只要把所有排列情况全部算一遍,看他们谁需要调换的次数最多,就输出那种排列方式。
有了这个想法之后,我发现自己不知道怎么算调换次数QAQ。然后看了dalao的算法。
1 2 3 4 5 6 7 8 9 10 for (int i=0 ;i<4 ;++i){ for (int j=0 ;j<4 ;++j){ if (i==j) continue ; int num = 0 ; for (int k=0 ;k<slen;++k){ if (a[k]==i) score[i][j]+=num; if (a[k]==j) num++; } } }
程序结束之后,s c o r e [ i ] [ j ] score[i][j] s c o r e [ i ] [ j ] 中存储的就是每个i i i 类型的字符,前面有多少个j j j 类型的字符。那么,很显然,如果要把所有i i i 类型字符调换到最前面,那么花费的操作次数就是∑ j = 0 4 s c o r e [ i ] [ j ] \sum\limits_{j=0}^4score[i][j] j = 0 ∑ 4 s c o r e [ i ] [ j ] 。然后,如果k k k 类型字符,要调换到i i i 的后面,那么花费的操作次数就是∑ j = 0 4 s c o r e [ k ] [ j ] − s c o r e [ k ] [ i ] \sum\limits_{j=0}^4score[k][j]-score[k][i] j = 0 ∑ 4 s c o r e [ k ] [ j ] − s c o r e [ k ] [ i ] 。以此类推,当确定了字符串的排列p p p 后,则可以算出当前排列需要花费的操作次数n o w now n o w ,n o w = ∑ i = 0 4 ∑ j = i + 1 4 s c o r e [ p i ] [ p j ] now=\sum\limits_{i=0}^4\sum\limits_{j=i+1}^4score[p_i][p_j] n o w = i = 0 ∑ 4 j = i + 1 ∑ 4 s c o r e [ p i ] [ p j ] 。好巧妙!真的写的很巧妙很漂亮啊!
完整的AC代码在这里啦:
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 48 49 50 51 52 53 54 55 {% raw %} string s; string ex="ANOT" ; map<char ,int > ma = {{'A' ,0 },{'N' ,1 },{'O' ,2 },{'T' ,3 }}; const int Max_n = 1e5 + 10 ;int a[Max_n];ll cnt[4 ], score[4 ][4 ]; void init () { memset (cnt,0 ,sizeof (cnt)); memset (score,0 ,sizeof (score)); } void solve () { cin >> s; int slen = s.size (); init (); for (int i=0 ;i<slen;++i){ a[i] = ma[s[i]]; cnt[a[i]]++; } for (int i=0 ;i<4 ;++i){ for (int j=0 ;j<4 ;++j){ if (i==j) continue ; int num = 0 ; for (int k=0 ;k<slen;++k){ if (a[k]==i) score[i][j]+=num; if (a[k]==j) num++; } } } ll res = -1 ; vector<int > best; vector<int > p = {0 ,1 ,2 ,3 }; do { ll now = 0 ; for (int i=0 ;i<4 ;++i){ for (int j=i+1 ;j<4 ;++j){ now += score[p[i]][p[j]]; } } if (now > res){ res = now; best = p; } }while (next_permutation (p.begin (), p.end ())); string ans; for (int i=0 ;i<4 ;++i){ for (int j=0 ;j<cnt[best[i]];++j){ ans += ex[best[i]]; } } cout << ans << endl; } {% endraw %}