数学提高
此部分由我前 队友完成。浏览体验可能会有所不同因为水平比我强
有些地方无法正常浏览,可能是因为我队友有些用的LateX语法,markdown渲染不出来
常用数学技巧和公式
1. 常用公式
完全立方公式
( a + b ) 3 = a 3 + 3 a 2 b + 3 a b 2 + b 3 = a 3 + b 3 + 3 a b ( a + b ) (a+b)^3=a^3+3a^2b+3ab^2+b^3=a^3+b^3+3ab(a+b) ( a + b ) 3 = a 3 + 3 a 2 b + 3 a b 2 + b 3 = a 3 + b 3 + 3 a b ( a + b )
a 3 + b 3 = ( a + b ) 3 − 3 a b ( a + b ) = ( a + b ) ( a 2 + b 2 − a b ) a^3+b^3=(a+b)^3-3ab(a+b)=(a+b)(a^2+b^2-ab) a 3 + b 3 = ( a + b ) 3 − 3 a b ( a + b ) = ( a + b ) ( a 2 + b 2 − a b )
( a − b ) 3 = a 3 − 3 a 2 b + 3 a b 2 − b 3 = a 3 − b 3 + 3 a b ( b − a ) (a-b)^3=a^3-3a^2b+3ab^2-b^3=a^3-b^3+3ab(b-a) ( a − b ) 3 = a 3 − 3 a 2 b + 3 a b 2 − b 3 = a 3 − b 3 + 3 a b ( b − a )
a 3 − b 3 = ( a − b ) 3 + 3 a b ( a − b ) = ( a − b ) ( a 2 + b 2 + a b ) a^3-b^3=(a-b)^3+3ab(a-b)=(a-b)(a^2+b^2+ab) a 3 − b 3 = ( a − b ) 3 + 3 a b ( a − b ) = ( a − b ) ( a 2 + b 2 + a b )
(当然,使用二项式定理也能自然的推出来,但是在使用数学公式构造时可能没那么快想起来对应的公式)
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
(也就是相同的某个数构成的数字,可以分解成111... × k 111...\times k 1 1 1 . . . × k 的形式,再把111... 111... 1 1 1 . . . 变成1 0 x − 1 9 \frac{10^x-1} 9 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 + . . .
(也就是一个从左往右的每一位不降的数,可以被分解成若干个111.. 111.. 1 1 1 . . 相加
( x − 1 ) 3 + ( x − 1 ) 3 − x 3 − x 3 = 6 x (x-1)^3+(x-1)^3-x^3-x^3=6x ( x − 1 ) 3 + ( x − 1 ) 3 − x 3 − x 3 = 6 x
102780J的神秘构造公式,具体见后面列出的题目。
任何一个大于2的奇数x x x 都可以写成完全平方差应该叫这个吧大概 的形式:x = a 2 − b 2 x=a^2-b^2 x = a 2 − b 2
假设x = 2 n + 1 x=2n+1 x = 2 n + 1 ,显然2 n + 1 = ( n + 1 ) 2 − n 2 2n+1=(n+1)^2-n^2 2 n + 1 = ( n + 1 ) 2 − n 2
随机化
mt19937生成随机数
在评测机上,R A N D _ M A X RAND\_MAX R A N D _ M A X 很小,只有32767,r a n d ( ) rand() r a n d ( ) 和r a n d _ s h u f f l e ( ) rand\_shuffle() r a n d _ s h u f f l e ( ) 的函数尽量不要使用。而M e r s e n n e T w i s t e r Mersenne~Twister M e r s e n n e T w i s t e r 算法提供了另一种随机数生成方法,用到的质数为2 19937 − 1 2^{19937}-1 2 1 9 9 3 7 − 1 ,这也是名字的由来。在随机化题目中,真的会有毒瘤出题人卡掉普通的r a n d ( ) rand() r a n d ( ) 随机数算法。
函数用法:
1 2 3 4 5 mt19937 rng (time(0 )) ;printf ("%u\n" , rng ());shuffle (a + 1 , a + n + 1 , rnd);
完整代码
1 2 3 4 5 6 7 8 9 10 11 12 #include <cstdio> #include <chrono> #include <random> using namespace std;mt19937 rng (chrono::steady_clock::now().time_since_epoch().count()) ;int main () { printf ("%u\n" , rng ()); return 0 ; }
矩阵乘法
矩阵的封装,重载运算符
例题:p1306 斐波那契数列 计算gcd ( f n , f m ) \gcd(f_n, f_m) g cd( f n , f m ) 。
根据数学基础篇的斐波那契数列那一节的性质,gcd ( f n , f m ) = f ( gcd ( n , m ) ) \gcd(f_n,f_m)=f(\gcd(n,m)) g cd( f n , f m ) = f ( g cd( n , 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 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 112 113 114 #include <bits/stdc++.h> using namespace std;#define ll long long #define sz 2 const ll N = 2 , M = 1e8 ;ll read () { ll x = 0 , f=1 ;char ch; do {ch = getchar ();if (ch == '-' ) f=-1 ;}while (ch<'0' || ch>'9' ); do {x = x*10 + (ch-'0' );ch = getchar ();}while (ch>='0' && ch<='9' ); return x*f; } struct mat { ll a[sz][sz]; inline mat () { memset (a, 0 , sizeof (a)); } inline mat (ll _a[sz][sz]) { for (int i=0 ;i<sz;++i) { for (int j=0 ;j<sz;++j) { a[i][j] = _a[i][j]; } } } inline void operator = (mat x) { for (int i=0 ; i<sz;++i) { for (int j=0 ;j<sz;++j) { a[i][j] = x.a[i][j]; } } } inline mat operator + (const mat& T) const { mat res; for (int i=0 ;i<sz;++i){ for (int j=0 ;j<sz;++j){ res.a[i][j] = (a[i][j] + T.a[i][j]) % M; } } return res; } inline mat operator - (const mat& T) const { mat res; for (int i=0 ;i<sz;++i){ for (int j=0 ;j<sz;++j){ res.a[i][j] = (a[i][j] - T.a[i][j]) % M; } } return res; } inline mat operator * (const mat& T) const { mat res; ll r; for (int i=0 ;i<sz;++i){ for (int k=0 ;k<sz;++k){ r = a[i][k]; for (int j=0 ;j<sz;++j){ res.a[i][j] = (res.a[i][j] + T.a[k][j] * r % M) % M; } } } return res; } inline mat operator ^ (ll x) const { mat res, b; for (int i=0 ;i<sz;++i) res.a[i][i] = 1 ; for (int i=0 ;i<sz;++i){ for (int j=0 ;j<sz;++j){ b.a[i][j] = a[i][j] % M; } } while (x){ if (x & 1 ) res = res * b; b = b * b; x >>= 1 ; } return res; } }; void mul (ll c[], ll a[], ll b[][N]) { ll t[N] = {0 }; for (int i=0 ;i<N;++i){ for (int j=0 ;j<N;++j){ t[i] = (a[j] * b[j][i] % M + t[i]) % M; } } memcpy (c, t, sizeof (t)); } ll Fibonacci (ll n) { ll _a[N][N] = { {0 , 1 }, {1 , 1 } }; mat A (_a) ; ll f1[N] = {1 , 1 }; A = A ^ (n - 1 ); mul (f1, f1, A.a); return f1[0 ]; } ll gcd (ll a, ll b) { return b == 0 ? a : gcd (b, a % b); } int main (void ) { ll n, m; n = read (), m = read (); ll GCD = gcd (n, m); printf ("%lld\n" , Fibonacci (GCD)); return 0 ; }
不封装 直接写函数计算× ÷ a k \times \div a^k × ÷ a k
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 void mul (int c[], int a[], int b[][N]) { int t[N] = {0 }; for (int i=0 ;i<N;++i){ for (int j=0 ;j<N;++j){ t[i] = (1LL * a[j] * b[j][i] + t[i]) % m; } } memcpy (c, t, sizeof (t)); } void mul (int c[][N], int a[][N], int b[][N]) { int t[N][N] = {0 }; for (int i=0 ;i<N;++i){ for (int j=0 ;j<N;++j){ for (int k=0 ;k<N;++k){ t[i][j] = (1LL * a[i][k] * b[k][j] + t[i][j]) % m; } } } memcpy (c, t, sizeof (t)); } void qmi (int a[][N], int b, int p) { int t[N][N]; for (int i=0 ;i<N;++i){ for (int j=0 ;j<N;++j){ t[i][j] = (i == j); } } while (b){ if (b & 1 ) mul (t, t, a); mul (a, a, a); b >>= 1 ; } memcpy (a, t, sizeof (t)); }
矩阵乘法应用
(1) 前置知识:矩阵快速幂
和快速幂一样,只不过对象是矩阵。
(2) 应用范围:递推式操作
一般表现为如下形式:某个行向量X n = [ a n a n − 1 . . a n − k + 1 ] X_n=[a_n~~ a_{n-1}~..a_{n-k+1}] X n = [ a n a n − 1 . . a n − k + 1 ] ,边界X 1 = [ a k − 1 a k − 2 . . a 0 ] X_1=[a_{k-1}~~a_{k-2}~..a_0] X 1 = [ a k − 1 a k − 2 . . a 0 ] ,可以找到某个矩阵A A A ,满足:X n = X n − 1 × A X_n=X_{n-1}\times A X n = X n − 1 × A ,则可以得到X n = X 1 × A n − 1 X_n=X_1\times A^{n-1} X n = X 1 × A n − 1 。注意:一定要保证A A A 中没有变量。
由于矩阵具有结合律,我们可以先求出B = A n − 1 m o d P B=A^{n-1}\mod P B = A n − 1 m o d P ,然后再计算X 1 × B X_1\times B X 1 × B ,即可求出X n X_n X n ,X n X_n X n 的第一个元素就是a n a_n a n 。
(3) 时间复杂度:O ( log n ) O(\log n) O ( log n ) 但是大常数ORZ 。
(4) 例题+模板
① AcWing1303 斐波那契前n项和
现有Fibonacci数列如下:f 1 = 1 , f 2 = 1 , f n = f n − 2 + f n − 1 f_1=1,f_2=1,f_n=f_{n-2}+f_{n-1} f 1 = 1 , f 2 = 1 , f n = f n − 2 + f n − 1 。
定义S n = ∑ i = 1 n f i S_n=\sum\limits _{i=1}^n f_i S n = i = 1 ∑ n f i ,对于给定的n , m n,m n , m ,求S n m o d m S_n \mod m S n m o d m 。
数据范围:1 ≤ n ≤ 2 × 1 0 9 ; 1 ≤ m ≤ 1 0 9 + 10 1\leq n \leq 2\times 10^9;1\leq m \leq 10^9 + 10 1 ≤ n ≤ 2 × 1 0 9 ; 1 ≤ m ≤ 1 0 9 + 1 0
首先啊,我们知道对于任意n > 2 n>2 n > 2 ,f n f_n f n 只和它的前两项有关,并且,S n + 1 = S n + f n + 1 S_{n+1}=S_{n}+f_{n+1} S n + 1 = S n + f n + 1 ,
那么只要我们知道了f n , f n + 1 , S n f_{n},f_{n+1},S_n f n , f n + 1 , S n ,就可以推出f n + 2 , S n + 1 f_{n+2},S_{n+1} f n + 2 , S n + 1 的值了。
因此定义X n = [ f n , f n + 1 , S n ] X_n=[f_n,f_{n+1},S_n] X n = [ f n , f n + 1 , S n ] ,则X n + 1 = [ f n + 1 , f n + 2 , S n + 1 ] X_{n+1}=[f_{n+1},f_{n+2},S_{n+1}] X n + 1 = [ f n + 1 , f n + 2 , S n + 1 ]
那么[ f n , f n + 1 , S n ] × [ 0 1 0 1 1 1 0 0 1 ] = [ f n + 1 , f n + 2 , S n + 1 ] [f_n,f_{n+1},S_n]\times \begin{bmatrix} 0~~1~~0 \\ 1~~1~~1 \\ 0~~0~~1 \end{bmatrix}=[f_{n+1},f_{n+2},S_{n+1}] [ f n , f n + 1 , S n ] × ⎣ ⎡ 0 1 0 1 1 1 0 0 1 ⎦ ⎤ = [ f n + 1 , f n + 2 , S n + 1 ]
因此,A = [ 0 1 0 1 1 1 0 0 1 ] A= \begin{bmatrix} 0~~1~~0 \\ 1~~1~~1 \\ 0~~0~~1 \end{bmatrix} A = ⎣ ⎡ 0 1 0 1 1 1 0 0 1 ⎦ ⎤ ,求出A n − 1 m o d m A^{n-1}\mod m A n − 1 m o d m 后,计算X 1 × A n − 1 m o d m X_1\times A^{n-1}\mod m X 1 × A n − 1 m o d m 即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 int n, m;const int N = 3 ;int main (void ) { scanf ("%d%d" , &n, &m); int f1[N] = {1 , 1 , 1 }; int a[N][N] = { {0 , 1 , 0 }, {1 , 1 , 1 }, {0 , 0 , 1 } }; qmi (a, n - 1 , m); mul (f1, f1, a); printf ("%d\n" , f1[2 ]); return 0 ; }
② 佳佳的斐波那契
这次是求解T ( n ) = ( f 1 + 2 f 2 + . . + n f n ) m o d m T(n)=(f_1+2f_2+..+nf_n)\mod m T ( n ) = ( f 1 + 2 f 2 + . . + n f n ) m o d m 的值,数据范围:1 ≤ n , m ≤ 2 31 − 1 1\leq n,m\leq 2^{31}-1 1 ≤ n , m ≤ 2 3 1 − 1
当所求T ( n ) T(n) T ( n ) 不能被直接构造时,可以考虑用两个可以算出的数值构造出T ( n ) T(n) T ( n ) 的答案。
由上题可以看出,f n , f n + 1 , S n f_n,f_{n+1},S_n f n , f n + 1 , S n 都是可以比较轻松算出来的。
又{ n S n − T n = ( n − 1 ) f 1 + ( n − 2 ) f 2 + . . + f n − 1 ( n + 1 ) S n + 1 − T n + 1 = n f 1 + ( n − 1 ) f 2 + . . + 2 f n − 1 + f n \begin{cases} nS_n-T_n=(n-1)f_1+(n-2)f_2+..+f_{n-1} \\ (n+1)S_{n+1}-T_{n+1}=nf_1+(n-1)f_2+..+2f_{n-1}+f_n \end{cases} { n S n − T n = ( n − 1 ) f 1 + ( n − 2 ) f 2 + . . + f n − 1 ( n + 1 ) S n + 1 − T n + 1 = n f 1 + ( n − 1 ) f 2 + . . + 2 f n − 1 + f n
定义P n = n S n − T n P_n=nS_n-T_n P n = n S n − T n 可得P n + 1 − P n = S n P_{n+1}-P_n=S_n P n + 1 − P n = S n ,而T n = n S n − P n T_n=nS_n-P_n T n = n S n − P n 。
下面就很好算了,定义X n = [ f n , f n + 1 , S n , P n ] X_n=[f_n,f_{n+1},S_n,P_n] X n = [ f n , f n + 1 , S n , P n ] ,X n + 1 = [ f n + 1 , f n + 2 , S n + 1 , P n + 1 ] X_{n+1}=[f_{n+1},f_{n+2},S_{n+1},P_{n+1}] X n + 1 = [ f n + 1 , f n + 2 , S n + 1 , P n + 1 ]
则[ f n , f n + 1 , S n , P n ] × [ 0 1 0 0 1 1 1 0 0 0 1 1 0 0 0 1 ] = [ f n + 1 , f n + 2 , S n + 1 , P n + 1 ] [f_n,f_{n+1},S_n,P_n]\times \begin{bmatrix} 0~~1~~0~~0\\ 1~~1~~1~~0\\ 0~~0~~1~~1\\ 0~~0~~0~~1 \end{bmatrix}=[f_{n+1},f_{n+2},S_{n+1},P_{n+1}] [ f n , f n + 1 , S n , P n ] × ⎣ ⎢ ⎢ ⎡ 0 1 0 0 1 1 1 0 0 0 1 1 0 0 0 1 ⎦ ⎥ ⎥ ⎤ = [ f n + 1 , f n + 2 , S n + 1 , P n + 1 ]
因此,A = [ 0 1 0 0 1 1 1 0 0 0 1 1 0 0 0 1 ] A=\begin{bmatrix} 0~~1~~0~~0\\ 1~~1~~1~~0\\ 0~~0~~1~~1\\ 0~~0~~0~~1 \end{bmatrix} A = ⎣ ⎢ ⎢ ⎡ 0 1 0 0 1 1 1 0 0 0 1 1 0 0 0 1 ⎦ ⎥ ⎥ ⎤ ,然后算出P n , S n P_n,S_n P n , S n ,则T n = n S n − P n T_n=nS_n-P_n T n = n S n − P n ,本题结束。
③ P7453 大魔法师
线段树维护矩阵+矩阵乘法。
④ Loj6208 树上询问
线段树+树链剖分+矩阵乘法
⑤ AcWing1305 GT考试
首先, 设f [ i ] [ j ] f[i][j] f [ i ] [ j ] 为到第i个数为止, 最后j个数和不吉利的串t的前缀相同的数量。那么, f [ i − 1 ] [ j ] − > f [ i ] [ k ] f[i-1][j] -> f[i][k] f [ i − 1 ] [ j ] − > f [ i ] [ k ] 的话, 就要枚举当前匹配的前缀长度是j j j ,第i i i 个点取0 − 9 0-9 0 − 9 的时候, 分别能够转移到哪些k k k
显然, 这个转移是固定的, 可以用k m p kmp k m p 算出来, 这就说明了f [ i − 1 ] − > f [ i ] f[i-1]->f[i] f [ i − 1 ] − > f [ i ] 的递推可以有固定的没有变量的转移矩阵,也就是说, 可以用矩阵乘法来做。
假如有0 − 9 0-9 0 − 9 一共10 10 1 0 个数, 我们在第i + 1 i+1 i + 1 个位置填c c c , 就可以利用k m p kmp k m p 求出来此时匹配到了第j j j 个位置
也就是说, 当前已经填好了第i i i 个数, 准备在第i + 1 i+1 i + 1 的位置填c c c 的时候, 可以转移到j j j
f [ i ] [ k ] = f [ i − 1 ] [ 0 ] × a [ 0 ] [ k ] + f [ i − 1 ] [ 1 ] × a [ 1 ] [ k ] + . . + f [ m − 1 ] [ k ] × a [ m − 1 ] [ k ] f[i][k] = f[i - 1][0]\times a[0][k] + f[i-1][1]\times a[1][k] + .. + f[m-1][k]\times a[m-1][k] f [ i ] [ k ] = f [ i − 1 ] [ 0 ] × a [ 0 ] [ k ] + f [ i − 1 ] [ 1 ] × a [ 1 ] [ k ] + . . + f [ m − 1 ] [ k ] × a [ m − 1 ] [ k ]
我们去累计这样i − > j i->j i − > j 的贡献, 就得到了我们的转移矩阵
X i = X i − 1 × A X_{i}= X_{i-1} \times A X i = X i − 1 × 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 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 #include <bits/stdc++.h> using namespace std;#define ll long long int n, m, M;const int N = 20 ;int nxt[N + 5 ];char s[N + 5 ];int mod (int a, int b) { return (a % b + b) % b; } void kmp () { for (int i=2 , j=0 ;i<=m;++i){ while (j && s[j + 1 ] != s[i]) j = nxt[j]; if (s[j + 1 ] == s[i]) ++j; nxt[i] = j; } } void mul (int c[], int a[], int b[][N]) { int t[N] = {0 }; for (int i=0 ;i<m;++i){ for (int j=0 ;j<m;++j){ t[i] = (t[i] + a[j] * b[j][i]) % M; } } memcpy (c, t, sizeof (t)); } void mul (int c[][N], int a[][N], int b[][N]) { int t[N][N] = {0 }; for (int i=0 ;i<m;++i){ for (int j=0 ;j<m;++j){ for (int k=0 ;k<m;++k){ t[i][j] = (t[i][j] + a[i][k] * b[k][j]) % M; } } } memcpy (c, t, sizeof (t)); } void qmi (int a[][N], int b) { int t[N][N] = {0 }; for (int i=0 ;i<m;++i){ for (int j=0 ;j<m;++j){ t[i][j] = (i == j); } } while (b){ if (b & 1 ) mul (t, t, a); mul (a, a, a); b >>= 1 ; } memcpy (a, t, sizeof (t)); } int main (void ) { ios::sync_with_stdio (false ); cin.tie (0 );cout.tie (0 ); cin >> n >> m >> M; cin >> s + 1 ; kmp (); int a[N][N] = {0 }, f[N] = {1 }; for (int i=0 ;i<m;++i){ for (char c='0' ;c<='9' ;++c){ int j = i; while (j && s[j + 1 ] != c) j = nxt[j]; if (s[j + 1 ] == c) ++j; if (j < m) ++a[i][j]; } } qmi (a, n); mul (f, f, a); int res = 0 ; for (int i=0 ;i<m;++i){ res = mod (res + f[i], M); } cout << res << endl; return 0 ; }
组合计数
组合数常用计算方法
这两个方法都是嗯……比较抽象和灵活的,在此就以一道例题AcWing1312序列统计 的形式简单讲下。
约定:题目中的 N , L , R N,L,R N , L , R 用 n , l , r n,l,r n , l , r 表示。
寄 有一个写的不错的题解,直接看他的吧,有什么不详细的地方我再补充0.0
1. 映射法
2. 隔板法
隔板法我在基础篇里也有介绍,在这里,隔板法是要求所有盒子非空的 ,这是应用隔板法的一个前提条件。而在法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 #include <bits/stdc++.h> using namespace std;#define ll long long int n, L, R;const int M = 1e6 + 3 ;int mod (int a, int b) { return (a % b + b) % b; } int qmi (int a, int b, int p) { int res = 1 % p; while (b){ if (b & 1 ) res = (ll) res * a % p; a = (ll) a * a % p; b >>= 1 ; } return res; } int C (int a, int b, int p) { if (b > a) return 0 ; int up = 1 , down = 1 ; for (int i=1 , j=a;i<=b;++i, --j){ up = (ll)up * j % p; down = (ll)down * i % p; } int res = (ll)up * qmi (down, p - 2 , p) % p; return res; } int lucas (int a, int b, int p) { if (a < p && b < p) return C (a, b, p); return (ll)lucas (a / p, b / p, p) * C (a % p, b % p, p) % p; } int main (void ) { int t; scanf ("%d" , &t); while (t--){ scanf ("%d%d%d" , &n, &L, &R); int res = lucas (R - L + n + 1 , R - L + 1 , M); printf ("%d\n" , mod (res - 1 , M)); } return 0 ; }
悬线法
基础模板
oi-wiki的定义:https://oi-wiki.org/misc/hoverline/
一个简单的题目,简单理解悬线法的过程:
SP1805 Largest Rectangle in a Histogram
https://www.luogu.com.cn/problem/SP1805
首先,n n n 个矩形就相当于n n n 条悬线,我们知道最大的面积肯定是由某一个悬线向左右扫过而形成的。那么悬线的扩展,显然的满足某一递推关系,可以帮助我们将复杂度由原来的O ( N 2 ) O(N^2) O ( N 2 ) 变成O ( N ) O(N) O ( N ) 。
定义l i l_i l i 为当前的i i i 位置能扩展到的悬线的最左端,初始时l i = i l_i=i l i = i 。假设已经处理好了前i − 1 i-1 i − 1 个位置的答案,那么当h i ≤ h i − 1 h_i\leq h_{i-1} h i ≤ h i − 1 时,i i i 也能扩展到i − 1 i-1 i − 1 能扩展到的位置。如果h i ≤ h l i − 1 − 1 h_i\leq h_{l_{i-1}-1} h i ≤ h l i − 1 − 1 时,又可以接着往前扩展……直到扩展到边界,我们就停止。因此对于∀ i \forall i ∀ i ,我们有
1 while (L[i] > 1 && a[i] <= a[L[i] - 1 ]) L[i] = L[L[i] - 1 ];
同样的,假如我们已经处理好i + 1 i+1 i + 1 到n n n 的答案,r i r_i r i 也能不断的向右扩展。
1 while (R[i] < n && a[i] <= a[R[i] + 1 ]) R[i] = R[R[i] + 1 ];
那么完整代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 int n;const int N = 1e5 + 5 ;int a[N], L[N], R[N];void solve () { for (int i=1 ;i<=n;++i){ scanf ("%d" , &a[i]); L[i] = R[i] = i; } for (int i=1 ;i<=n;++i){ while (L[i] > 1 && a[i] <= a[L[i] - 1 ]) L[i] = L[L[i] - 1 ]; } for (int i=n;i>=1 ;--i){ while (R[i] < n && a[i] <= a[R[i] + 1 ]) R[i] = R[R[i] + 1 ]; } ll res = 0 ; for (int i=1 ;i<=n;++i){ res = max (res, 1LL * a[i] * (R[i] - L[i] + 1 )); } printf ("%lld\n" , res); }
UVA1619/POJ2796 Feel Good
给出长度为n n n 的数组a [ n ] a[n] a [ n ] ,找到一个子区间,使得子区间内的最小值与区间内所有元素和的乘积最大,如果有多个答案,输出长度最小的答案,如果仍有多个答案,输出最左端序号最小的答案。
枚举这个最小值,它一旦向左右扩展,就肯定会增加这个乘积的值,这样的话,又变成了一个悬线法求最大子矩形的问题。
数据范围:1 ≤ n ≤ 1 0 5 1\leq n \leq 10^5 1 ≤ n ≤ 1 0 5 我恨UVA的多组数据和格式
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 int n;const int N = 1e5 + 5 ;int a[N], L[N], R[N];ll pre[N]; bool fst = 1 ;void solve () { if (!fst){ puts ("" ); } fst = 0 ; for (int i=1 ;i<=n;++i){ a[i] = read (); pre[i] = pre[i - 1 ] + a[i]; L[i] = R[i] = i; } for (int i=1 ;i<=n;++i){ while (L[i] > 1 && a[i] <= a[L[i] - 1 ]) L[i] = L[L[i] - 1 ]; } for (int i=n;i>=1 ;--i){ while (R[i] < n && a[i] <= a[R[i] + 1 ]) R[i] = R[R[i] + 1 ]; } ll res = 0 ; int aL = 1 , aR = 1 ; for (int i=1 ;i<=n;++i){ ll cur = (pre[R[i]] - pre[L[i] - 1 ]) * a[i]; if (cur > res){ res = cur, aL = L[i], aR = R[i]; } else if (cur == res){ if (R[i] - L[i] < aR - aL){ aL = L[i], aR = R[i]; } else if (R[i] - L[i] == aR - aL){ if (L[i] < aL){ aL = L[i], aR = R[i]; } } } } printf ("%lld\n%d %d\n" , res, aL, aR); } int main (void ) { while (~scanf ("%d" , &n)){ if (n == 0 ){ puts ("" ); } else solve (); } return 0 ; }
最大子矩形:p4147 玉蟾宫
嗯……差不多捏,问题。oiwiki留的课后习题也写了,就不放出来了。
xcpc真题 King’s Children
有一个n × m n\times m n × m 的网格,里面有最多A t o Z A~to ~Z A t o Z 个孩子,king要把这个网格分成若干矩形,要满足:
每个矩形必须精确的包括一个孩子
每个格子都必须精确的属于1个矩形(也就是矩形不相交的分完整个大网格)
包括了A A A 的矩形的面积尽可能大
数据范围1 ≤ n , m ≤ 1000 1\leq n,m\leq 1000 1 ≤ n , m ≤ 1 0 0 0
K题我的思路就是,每个矩形都用悬线法进行选取和填充。但是,由于填充顺序的不同,有极低的概率出现最后有矩形没有被完全填上的情况,因此做一个简单的check,如果填充错误,则随机化顺序,重新填充答案。大概2~3次随机后就不可能出现还没填充的情况了,所以这个复杂度是完全可行的。
代码:
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 112 113 114 115 int n, m, sx, sy;const int N = 1000 + 5 ;char s[N][N], ss[N][N];int U[N], L[N], R[N];struct node { char ch;pii cor; }; vector<node> alp; bool check (pii s, pii a, pii b) { return (s.xx >= a.xx && s.xx <= b.xx) && (s.yy >= a.yy && s.yy <= b.yy); } void putin (char ch, pii cor) { int res = 0 ; pii r1 = cor, r2 = cor; for (int j=1 ;j<=m;++j) U[j] = 0 ; for (int i=1 ;i<=n;++i){ for (int j=1 ;j<=m;++j){ if (s[i][j] == '.' || s[i][j] == ch){ U[j]++; } else { U[j] = 0 ; } L[j] = R[j] = j; } for (int j=1 ;j<=m;++j){ while (L[j] > 1 && U[j] <= U[L[j] - 1 ]) L[j] = L[L[j] - 1 ]; } for (int j=m;j>=1 ;--j){ while (R[j] < m && U[j] <= U[R[j] + 1 ]) R[j] = R[R[j] + 1 ]; } for (int j=1 ;j<=m;++j){ int cur = U[j] * (R[j] - L[j] + 1 ); pii c1 = pii (i - U[j] + 1 , L[j]), c2 = pii (i, R[j]); if (cur > res && check (cor, c1, c2)){ res = cur, r1 = c1, r2 = c2; } } } for (int i=r1.xx;i<=r2.xx;++i){ for (int j=r1.yy;j<=r2.yy;++j){ if (s[i][j] == '.' ){ s[i][j] = 'a' + (ch - 'A' ); } } } } void solve () { for (int i=1 ;i<=n;++i){ for (int j=1 ;j<=m;++j){ s[i][j] = ss[i][j]; } } int alen = alp.size (), t = alen - 1 ; if (alen > 1 ){ for (int i=0 ;i<alen-1 ;++i){ int x = 1 + rand () % t; swap (alp[x], alp[t]); t--; } } for (int i=0 ;i<alp.size ();++i){ putin (alp[i].ch, alp[i].cor); } } bool isOK () { for (int i=1 ;i<=n;++i){ for (int j=1 ;j<=m;++j){ if (s[i][j] == '.' ){ return false ; } } } return true ; } int main (void ) { srand (time (NULL )); int T; T = 1 ; ios::sync_with_stdio (false ); cin.tie (0 );cout.tie (0 ); cin >> n >> m; for (int i=1 ;i<=n;++i){ for (int j=1 ;j<=m;++j){ cin >> ss[i][j]; if (ss[i][j] == 'A' ){ alp.push_back (node{'A' , pii (i, j)}); } } } for (int i=1 ;i<=n;++i){ for (int j=1 ;j<=m;++j){ if (ss[i][j] != '.' && ss[i][j] != 'A' ){ alp.push_back (node{ss[i][j], pii (i, j)}); } } } do { solve (); }while (!isOK ()); for (int i=1 ;i<=n;++i){ for (int j=1 ;j<=m;++j){ cout << s[i][j]; } cout << endl; } return 0 ; }
数论分块
简介
数论分块通常被用来以O ( n ) O(\sqrt n) O ( n ) 的复杂度快速计算形如∑ i = 1 n f ( i ) g ( ⌊ n i ⌋ ) \sum \limits_{i=1}^n f(i)g(\lfloor \frac n i \rfloor) i = 1 ∑ n f ( i ) g ( ⌊ i n ⌋ ) 的含有除法向下取整的和式,它的核心思想是将⌊ n i ⌋ \lfloor \frac n i \rfloor ⌊ i n ⌋ 相同的数打包同时计算,主要利用了Fubini定理。
证明
1. 证明时间复杂度为O ( n ) O(\sqrt n) O ( n )
**引理1 **对于任意一个正整数n n n ,⌊ n d ⌋ ( d ∈ [ 1 , n ] ) \lfloor \frac n d \rfloor(d\in[1,n]) ⌊ d n ⌋ ( d ∈ [ 1 , n ] ) 的数量级为n \sqrt n n 。
d ≤ n d\leq \sqrt n d ≤ n ,假设所有的⌊ n d ⌋ \lfloor \frac n d \rfloor ⌊ d n ⌋ 取值均不同,则存在n \sqrt n n 种结果
d > n d>\sqrt n d > n ,则⌊ n d ⌋ < n \lfloor \frac n d \rfloor<\sqrt n ⌊ d n ⌋ < n ,取值同样有n \sqrt n n 。
因此,所有的可能取值一定≤ 2 n \leq 2\sqrt n ≤ 2 n ,即整数分块的复杂度为O ( n ) O(\sqrt n) O ( n ) 。
2. 证明算法的正确性
引理2 对于∀ i , \forall i, ∀ i , 若满足⌊ n i ⌋ = C \lfloor \frac n i \rfloor=C ⌊ i n ⌋ = C ,则所有满足条件的i i i 一定是一段连续区间的集合。
反证法,若不是连续区间,则一定∃ t ∈ ( l , r ) \exist t\in(l,r) ∃ t ∈ ( l , r ) ,使得⌊ n l ⌋ = ⌊ n r ⌋ \lfloor \frac n l \rfloor=\lfloor \frac n r \rfloor ⌊ l n ⌋ = ⌊ r n ⌋ 且⌊ n l ⌋ ≠ ⌊ n t ⌋ \lfloor \frac n l \rfloor \neq \lfloor \frac n t \rfloor ⌊ l n ⌋ = ⌊ t n ⌋ 。
∵ t > l , t < r \because t>l,t<r ∵ t > l , t < r
∴ ⌊ n t ⌋ ≥ ⌊ n l ⌋ , ⌊ n t ⌋ ≤ ⌊ n r ⌋ \therefore \lfloor \frac n t \rfloor \geq \lfloor \frac n l \rfloor, \lfloor \frac n t \rfloor \leq \lfloor \frac n r \rfloor ∴ ⌊ t n ⌋ ≥ ⌊ l n ⌋ , ⌊ t n ⌋ ≤ ⌊ r n ⌋ ,即⌊ n t ⌋ = ⌊ n l ⌋ \lfloor \frac n t \rfloor=\lfloor \frac n l \rfloor ⌊ t n ⌋ = ⌊ l n ⌋ ,矛盾,故得证。
引理3 若满足⌊ n i ⌋ = C \lfloor \frac n i \rfloor=C ⌊ i n ⌋ = C 的i i i 集合为i ∈ [ l , r ] i\in[l,r] i ∈ [ l , r ] ,则只需要知道l l l ,就可以求出对应的C , r C,r C , r 。
显然,C = ⌊ n l ⌋ C=\lfloor \frac n l \rfloor C = ⌊ l n ⌋ ,下面就要证明r = ⌊ n ⌊ n l ⌋ ⌋ = ⌊ n C ⌋ r=\lfloor \frac{n}{\lfloor \frac n l \rfloor} \rfloor=\lfloor \frac n C \rfloor r = ⌊ ⌊ l n ⌋ n ⌋ = ⌊ C n ⌋
令p = ⌊ n C ⌋ p=\lfloor \frac n C \rfloor p = ⌊ C n ⌋ ,则n = p × C + k , k ∈ [ 0 , min { p , C } ) n=p\times C+k,k\in[0,\min\{p,C\}) n = p × C + k , k ∈ [ 0 , min { p , C } ) ,下面要证明p p p 是满足i i i 的性质的最大的整数。
p p p 满足i i i 的性质,即⌊ n p ⌋ = C \lfloor \frac n p \rfloor=C ⌊ p n ⌋ = C
p = n − k C p=\frac{n-k}{C} p = C n − k ,则n p = C × n n − k ≥ C × n n \frac n p=\frac{C\times n}{n-k}\geq \frac {C\times n}{n} p n = n − k C × n ≥ n C × n ,即n p ≥ C \frac n p \geq C p n ≥ C 。
又n m o d p = k , k < p n\mod p=k,k<p n m o d p = k , k < p ,则n − k < p × ( C + 1 ) n-k<p\times (C+1) n − k < p × ( C + 1 ) ,即⌊ n p ⌋ < C + 1 \lfloor \frac n p \rfloor<C+1 ⌊ p n ⌋ < C + 1
因此⌊ n p ⌋ = C \lfloor \frac n p \rfloor=C ⌊ p n ⌋ = C
p p p 是使得⌊ n i ⌋ = C \lfloor \frac n i \rfloor=C ⌊ i n ⌋ = C 成立的最大的数。
反证法,假如p p p 不是最大的数,则p + 1 p+1 p + 1 一定可以使得⌊ n p + 1 ⌋ = C \lfloor \frac n {p+1} \rfloor=C ⌊ p + 1 n ⌋ = C
∴ ( p + 1 ) × C = n − g ( g ∈ [ 0 , min { p + 1 , C } ) \therefore (p+1)\times C=n-g(g\in[0,\min\{p+1,C\}) ∴ ( p + 1 ) × C = n − g ( g ∈ [ 0 , min { p + 1 , C } )
∴ p × C + C = n − g \therefore p\times C + C=n-g ∴ p × C + C = n − g ,又p × C = n − k p\times C=n-k p × C = n − k
所以C = k − g C=k-g C = k − g ,又k , g ∈ [ 0 , C ) k,g\in[0,C) k , g ∈ [ 0 , C ) ,所以矛盾,得证。
综上,⌊ n C ⌋ \lfloor \frac n C \rfloor ⌊ C n ⌋ 即为令⌊ n i ⌋ = C \lfloor \frac n i \rfloor=C ⌊ i n ⌋ = C 的最大的数
模板
1 2 3 4 5 6 7 8 9 ll H (ll n) { ll res = 0 , l = 1 , r; while (l <= n){ r = n / (n / l); res = res + (r - l + 1 ) * (n / l); l = r + 1 ; } return res; }
例题
P2261余数之和
给出n , k n,k n , k ,计算G ( n , k ) = ∑ i = 1 n k m o d i G(n,k)=\sum\limits _{i=1}^n k \bmod i G ( n , k ) = i = 1 ∑ n k m o d i
数据范围:1 ≤ n , k ≤ 1 0 9 1\leq n,k\leq 10^9 1 ≤ n , k ≤ 1 0 9
(懒得自己写题解了,直接cpy洛谷题解orz)由题意得:a n s = ∑ i = 1 n k m o d i ans=∑_{i=1}^nk\bmod i a n s = ∑ i = 1 n k m o d i
我们知道,a m o d b = a − b × ⌊ a b ⌋ a \bmod b = a - b \times \lfloor \frac{a}{b} \rfloor a m o d b = a − b × ⌊ b a ⌋
因此,a n s = ∑ i = 1 n k − i × ⌊ k i ⌋ = n k − ∑ i = 1 n i × ⌊ k i ⌋ ans = \sum_{i=1}^{n} k - i \times \lfloor \frac{k}{i} \rfloor = nk - \sum_{i=1}^{n} i \times \lfloor \frac{k}{i} \rfloor a n s = ∑ i = 1 n k − i × ⌊ i k ⌋ = n k − ∑ i = 1 n i × ⌊ i k ⌋
首先枚举块的左边界 l l l ,并根据左边界和$ k$ 计算出右边界 r r r 。
令 t = ⌊ k l ⌋ t = \lfloor \frac{k}{l} \rfloor t = ⌊ l k ⌋ ,分两种情况讨论:
t ≠ 0 t \neq 0 t = 0 ,则 r = min ( ⌊ k t ⌋ , n ) r = \min (\lfloor \frac{k}{t} \rfloor , n) r = min ( ⌊ t k ⌋ , n ) ;
t = 0 t = 0 t = 0 ,则 r = n r = n r = n 。
右边界有了,每一块的和也就可以计算出了。
每一块的和 = = = 当前块的 t × t\times t × 当前块元素个数 × \times × 当前块 i i i 的平均值 = t × ( r − l + 1 ) × ( l + r ) ÷ 2 = t \times (r-l+1) \times (l+r) \div 2 = t × ( r − l + 1 ) × ( l + r ) ÷ 2
当前块处理完后,令 l = r + 1 l = r + 1 l = r + 1 ,开始计算下一块,直到计算至 n。
1 2 3 4 5 6 7 8 9 10 11 12 13 ll n, m; void solve () { n = read (), m = read (); ll res = n * m, l = 1 , r; while (l <= n){ if (m / l == 0 ) r = n; else r = min (n, m / (m / l)); res = res - (r - l + 1 ) * (m / l) * (l + r) / 2 ; l = r + 1 ; } printf ("%lld\n" , res); }
数论分块往往和莫比乌斯反演结合起来考察,因此进阶的部分放在可能会存在的莫反专题 中一起讲解。
常见数论定义
数论函数
**定义:**一个定义在整数集合上的实数或复数函数f ( n ) f(n) f ( n ) ,称为一个数论函数,又叫算术函数。:
**举例:p o t p ( n ) , μ ( n ) , φ ( n ) , π ( n ) pot _p (n),\mu(n),\varphi(n),\pi(n) p o t p ( n ) , μ ( n ) , φ ( n ) , π ( n ) **都是数论函数,n ( i ) = n n(i)=n n ( i ) = n :一直返回同一个常数n n n 。
加性函数与积性函数
定义
对于一个数论函数f : N → C f:N\rightarrow C f : N → C ,若∀ m , n \forall m,n ∀ m , n ,满足g c d ( m , n ) = 1 gcd(m,n)=1 g c d ( m , n ) = 1 ,有f ( m × n ) = f ( m ) + f ( n ) f(m\times n)=f(m)+f(n) f ( m × n ) = f ( m ) + f ( n )
则称f f f 为加性 的,这时定义给出f ( 1 ) = 0 f(1)=0 f ( 1 ) = 0 。
若∀ m , n \forall m,n ∀ m , n ,满足g c d ( m , n ) = 1 gcd(m,n)=1 g c d ( m , n ) = 1 ,有f ( m × n ) = f ( m ) × f ( n ) f(m\times n)=f(m)\times f(n) f ( m × n ) = f ( m ) × f ( n ) 则称f f f 为积性 的,这时定义给出f ( 1 ) = 1 f(1)=1 f ( 1 ) = 1 。
若去掉g c d ( m , n ) = 1 gcd(m,n)=1 g c d ( m , n ) = 1 这一条件仍然满足,则称f f f 为完全积性函数 。
判定函数为积性函数或完全积性函数
设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 ( 1 ) = 1 f(1)=1 f ( 1 ) = 1 且f ( n ) = ∏ i = 1 k f ( p i a i ) ⇔ f ( n ) f(n)=\prod_{i=1}^k f(p_i^{a^i}) \Leftrightarrow f(n) f ( n ) = ∏ i = 1 k f ( p i a i ) ⇔ f ( n ) 是积性函数 (充要条件)
f ( 1 ) = 1 f(1)=1 f ( 1 ) = 1 且f a 1 ( p 1 ) × f a 2 ( p 2 ) × . . × f a k ( p k ) ⇔ f ( n ) f^{a_1}(p_1)\times f^{a_2}(p_2)\times..\times f^{a_k}(p_k) \Leftrightarrow f(n) f a 1 ( p 1 ) × f a 2 ( p 2 ) × . . × f a k ( p k ) ⇔ f ( n ) 是完全积性函数 (充要条件)
这表明,一个积性函数完全由它在素数幂p i a i p_i^{a_i} p i a i 上的取值所确定;而完全积性函数则完全由它在素数p i p_i p i 上的取值所确定,我们由此可构造积性函数。
举例:常见的积性函数
单位元函数 :e ( n ) = [ n = 1 ] e(n)=[n=1] e ( n ) = [ n = 1 ] ,即e ( n ) = { 1 n = 1 0 n ≠ 1 e(n)=\begin{cases} 1 & n=1\\ 0 & n\neq 1 \end{cases} e ( n ) = { 1 0 n = 1 n = 1
它卷积上任意的数论函数仍然为原来的数论函数,即满足f × e = e × f = f f\times e=e\times f=f f × e = e × f = f
幂函数 :i d k ( n ) = n k id^k(n)=n^k i d k ( n ) = n k ,完全积性
单位函数 :i d ( n ) = n id(n)=n i d ( n ) = n ,完全积性,相当于i d 1 id^1 i d 1
恒等函数 :I ( n ) = 1 I(n)=1 I ( n ) = 1 (也就是常数值函数,只是刚好这个常数等于1),相当于i d 0 id^0 i d 0
欧拉函数 :φ ( n ) = ∑ i = 1 n [ g c d ( n , i ) = 1 ] × 1 \varphi(n)=\sum\limits _{i=1}^n[gcd(n,i)=1]\times 1 φ ( n ) = i = 1 ∑ n [ g c d ( n , i ) = 1 ] × 1
除数函数 :σ k ( n ) = ∑ d ∣ n d k \sigma_k(n)=\sum\limits _{d|n}d^k σ k ( n ) = d ∣ n ∑ d k ,表示n n n 的约数的k k k 次幂和
约数和函数 :σ ( n ) = σ 1 ( n ) = ∑ d ∣ n d \sigma(n)=\sigma_1(n)=\sum\limits _{d|n}d σ ( n ) = σ 1 ( n ) = d ∣ n ∑ d ,表示n n n 的约数之和
约数个数函数 :τ ( n ) = σ 0 ( n ) = ∑ d ∣ n 1 \tau(n)=\sigma_0(n)=\sum\limits _{d|n}1 τ ( n ) = σ 0 ( n ) = d ∣ n ∑ 1 ,表示n n n 的约数的个数,一般也写作d ( n ) d(n) d ( n )
莫比乌斯函数 :μ ( n ) \mu(n) μ ( n )
生成函数
狄利克雷卷积
定义
1. 狄利克雷卷积(Dirichlet Product)
设现在有两个数论函数f ( n ) , g ( n ) f(n),g(n) f ( n ) , g ( n ) ,那么它们的狄利克雷卷积(也叫狄利克雷乘积)也是一个数论函数。记它们的狄利克雷卷积为h ( n ) h(n) h ( n ) ,则有:
h ( n ) = ∑ d ∣ n f ( d ) g ( n d ) h(n)=\sum\limits _{d|n}f(d)g(\frac n d) h ( n ) = d ∣ n ∑ f ( d ) g ( d n ) ,或者说h ( n ) = ∑ x y = n f ( x ) g ( y ) h(n)=\sum\limits _{xy=n}f(x)g(y) h ( n ) = x y = n ∑ f ( x ) g ( y )
简记为h ( n ) = f ( n ) ∗ g ( n ) h(n)=f(n) * g(n) h ( n ) = f ( n ) ∗ g ( n )
2. 狄利克雷逆(Dirichlet inverse)
若f ∗ g = e f*g=e f ∗ g = e ,则称g g g 是f f f 的狄利克雷逆,记作f − 1 f^{-1} f − 1 。
计算狄利克雷逆
首先,当n = 1 n=1 n = 1 时
( f ∗ f − 1 ) ( 1 ) = ∑ d ∣ 1 f ( d ) f − 1 ( 1 d ) = f ( 1 ) f − 1 ( 1 ) (f*f^{-1})(1)=\sum\limits _{d|1}f(d)f^{-1}(\frac 1 d)=f(1)f^{-1}(1) ( f ∗ f − 1 ) ( 1 ) = d ∣ 1 ∑ f ( d ) f − 1 ( d 1 ) = f ( 1 ) f − 1 ( 1 )
又( f ∗ f − 1 ) ( 1 ) = e ( 1 ) = 1 (f*f^{-1})(1)=e(1)=1 ( f ∗ f − 1 ) ( 1 ) = e ( 1 ) = 1 ,所以f ( 1 ) f − 1 ( 1 ) = 1 f(1)f^{-1}(1)=1 f ( 1 ) f − 1 ( 1 ) = 1
f − 1 ( 1 ) = 1 f ( 1 ) f^{-1}(1)=\frac 1 {f(1)} f − 1 ( 1 ) = f ( 1 ) 1
这说明了,f − 1 f^{-1} f − 1 存在的必要条件 是f ( 1 ) ≠ 0 f(1)\neq 0 f ( 1 ) = 0 。
当n > 1 n>1 n > 1 时,有
( f ∗ f − 1 ) ( n ) = ∑ d ∣ n f ( d ) f − 1 ( n d ) = f ( 1 ) f − 1 ( n ) + ∑ d ∣ n , d > 1 f ( d ) f − 1 ( n d ) (f*f^{-1})(n)=\sum\limits _{d|n}f(d)f^{-1}(\frac n d)=f(1)f^{-1}(n)+\sum\limits _{d|n,d>1}f(d)f^{-1}(\frac n d) ( f ∗ f − 1 ) ( n ) = d ∣ n ∑ f ( d ) f − 1 ( d n ) = f ( 1 ) f − 1 ( n ) + d ∣ n , d > 1 ∑ f ( d ) f − 1 ( d n )
∵ ( f ∗ f − 1 ) ( n ) = e ( n ) = 0 \because (f*f^{-1})(n)=e(n)=0 ∵ ( f ∗ f − 1 ) ( n ) = e ( n ) = 0
∴ f ( 1 ) f − 1 ( n ) + ∑ d ∣ n , d > 1 f ( d ) f − 1 ( n d ) = 0 \therefore f(1)f^{-1}(n)+\sum\limits _{d|n,d>1}f(d)f^{-1}(\frac n d)=0 ∴ f ( 1 ) f − 1 ( n ) + d ∣ n , d > 1 ∑ f ( d ) f − 1 ( d n ) = 0
f − 1 ( n ) = − 1 f ( 1 ) ∑ d ∣ n , d > 1 f ( d ) f − 1 ( n d ) f^{-1}(n)=-\frac1 {f(1)}\sum\limits _{d|n,d>1}f(d)f^{-1}(\frac n d) f − 1 ( n ) = − f ( 1 ) 1 d ∣ n , d > 1 ∑ f ( d ) f − 1 ( d n )
综上,即可递归计算 狄利克雷逆
f − 1 ( n ) = { 1 f ( 1 ) n = 1 − 1 f ( 1 ) ∑ d ∣ n , d > 1 f ( d ) f − 1 ( n d ) n > 1 f^{-1}(n)=\begin{cases} \frac 1 {f(1)} & n=1 \\ -\frac1 {f(1)}\sum\limits _{d|n,d>1}f(d)f^{-1}(\frac n d) & n > 1 \end{cases} f − 1 ( n ) = ⎩ ⎨ ⎧ f ( 1 ) 1 − f ( 1 ) 1 d ∣ n , d > 1 ∑ f ( d ) f − 1 ( d n ) n = 1 n > 1
性质
交换律
( f ∗ g ) ( n ) = ∑ x y = n f ( x ) g ( y ) = ∑ y x = n = g ( y ) f ( x ) = ( g ∗ f ) ( n ) (f * g)(n)=\sum \limits_{xy=n}f(x)g(y)=\sum\limits _{yx=n}=g(y)f(x)=(g*f)(n) ( f ∗ g ) ( n ) = x y = n ∑ f ( x ) g ( y ) = y x = n ∑ = g ( y ) f ( x ) = ( g ∗ f ) ( n )
结合律
分配律
若f , g f,g f , g 为积性函数,则h = f ∗ g h=f*g h = f ∗ g 也是积性函数
若g , h = f ∗ g g, h=f*g g , h = f ∗ g 是积性函数,则f f f 也是积性函数
数论函数的卷积关系
0. 对于任意数论函数f f f 和恒等函数I I I ,有
( f ∗ I ) ( n ) = ∑ d ∣ n f ( d ) I ( n d ) = ∑ d ∣ n f ( d ) (f*I)(n)=\sum\limits_{d|n} f(d)I(\frac n d)=\sum\limits _{d|n}f(d) ( f ∗ I ) ( n ) = d ∣ n ∑ f ( d ) I ( d n ) = d ∣ n ∑ f ( d )
1. 幂函数与除数函数
( i d k ∗ I ) ( n ) = ∑ d ∣ n i d k ( n ) = ∑ n k = σ k ( n ) (id^k*I)(n)=\sum\limits _{d|n}id^k(n)=\sum\limits n^k=\sigma_k(n) ( i d k ∗ I ) ( n ) = d ∣ n ∑ i d k ( n ) = ∑ n k = σ k ( n )
即i d k ∗ I = σ k id^k * I=\sigma_k i d k ∗ I = σ k 。
2. 欧拉函数与恒等函数
( φ ∗ I ) = ∑ d ∣ n φ ( d ) = n = i d ( n ) (\varphi*I)=\sum\limits_{d|n}\varphi(d)=n=id(n) ( φ ∗ I ) = d ∣ n ∑ φ ( d ) = n = i d ( n )
3. 恒等函数与恒等函数
I ∗ I = d I *I=d I ∗ I = d (在莫比乌斯函数里证明过了)
莫比乌斯反演
莫比乌斯函数
1. 定义
由唯一分解定理,可以将正整数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 的形式,莫比乌斯函数μ ( n ) \mu(n) μ ( n ) 的定义为
μ ( n ) = { 1 n = 1 0 ∃ i , a i ≥ 2 ( − 1 ) k ∀ i , a i = 1 \mu(n)=\begin{cases} 1 & n=1 \\ 0 & \exist i, a_i\geq 2\\ (-1)^{k} & \forall i,a_i=1 \end{cases}
μ ( n ) = ⎩ ⎪ ⎨ ⎪ ⎧ 1 0 ( − 1 ) k n = 1 ∃ i , a i ≥ 2 ∀ i , a i = 1
2. 性质
性质1
∑ d ∣ n μ ( d ) = { 1 n = 1 0 n ≠ 1 \sum\limits _{d|n}\mu(d)=\begin{cases} 1 & n=1 \\ 0 & n \neq 1 \end{cases}
d ∣ n ∑ μ ( d ) = { 1 0 n = 1 n = 1
证明:设d d d 为n n n 的约数,则d = ∏ i = 1 k p i b i d=\prod_{i=1}^kp_i^{b_i} d = ∏ i = 1 k p i b i ,其中0 ≤ b i ≤ a i 0\leq b_i\leq a_i 0 ≤ b i ≤ a i 。
对于μ ( d ) \mu(d) μ ( d ) ,如果∃ b i ≥ 2 \exist b_i\geq 2 ∃ b i ≥ 2 ,则μ ( d ) = 0 \mu(d)=0 μ ( d ) = 0 。因此,有贡献的μ ( d ) \mu(d) μ ( d ) 一定为C k i × ( − 1 ) i C_k^i\times(-1)^i C k i × ( − 1 ) i ,也就是每个质数最多取一次。
则∑ d ∣ n μ ( d ) = ∑ i = 0 k C k i × ( − 1 ) i \sum\limits _{d|n}\mu(d)=\sum\limits _{i=0}^kC_k^i\times(-1)^i d ∣ n ∑ μ ( d ) = i = 0 ∑ k C k i × ( − 1 ) i ,又( a − b ) k = ∑ i = 0 k C k i a k b k − i (a-b)^k=\sum\limits_{i=0}^k C_k^i a^kb^{k-i} ( a − b ) k = i = 0 ∑ k C k i a k b k − i
( 1 − 1 ) k = ∑ i = 0 k C k i × ( − 1 ) k (1-1)^k=\sum\limits _{i=0}^kC_k^i\times (-1)^k ( 1 − 1 ) k = i = 0 ∑ k C k i × ( − 1 ) k ,故∑ d ∣ n μ ( d ) = 0 k = 0 \sum\limits _{d|n}\mu(d)=0^k=0 d ∣ n ∑ μ ( d ) = 0 k = 0
3. 与其他数论函数的关系
(1) μ ∗ I = e \mu * I = e μ ∗ I = e
证明:设n = ∏ i = 1 k p i a i , n ′ = ∏ i = 1 k p i n=\prod_{i=1}^kp_i^{a_i}, n'=\prod_{i=1}^kp_i n = ∏ i = 1 k p i a i , n ′ = ∏ i = 1 k p i
则( μ ∗ I ) ( n ) = ∑ d ∣ n μ ( d ) = ∑ d ∣ n ′ μ ( d ) = ∑ i = 0 k ( − 1 ) i (\mu*I)(n)=\sum\limits _{d|n}\mu(d)=\sum\limits _{d|n'}\mu(d)\\= \sum\limits _{i=0}^k(-1)^i ( μ ∗ I ) ( n ) = d ∣ n ∑ μ ( d ) = d ∣ n ′ ∑ μ ( d ) = i = 0 ∑ k ( − 1 ) i
呃,等等,好像性质一已经证明过了啊。( μ ∗ I ) ( n ) = [ n = 1 ] = e (\mu*I)(n)=[n=1]=e ( μ ∗ I ) ( n ) = [ n = 1 ] = e ,
因此,μ \mu μ 是I I I 的狄利克雷逆。
(2) μ ∗ i d = φ \mu * id = \varphi μ ∗ i d = φ
这个在基础篇的性质证明过了QWQ,不写辣
(3) μ ∗ d = I \mu * d=I μ ∗ d = I
证明:( I ∗ I ) ( n ) = ∑ d ∣ n I ( d ) = ∑ d ∣ n 1 = d ( n ) (I*I)(n)=\sum\limits _{d|n}I(d)=\sum\limits _{d|n}1=d(n) ( I ∗ I ) ( n ) = d ∣ n ∑ I ( d ) = d ∣ n ∑ 1 = d ( n )
∴ d = I ∗ I \therefore d=I*I ∴ d = I ∗ I ,又μ = I − 1 \mu=I^{-1} μ = I − 1
∴ μ ∗ d = I \therefore \mu * d=I ∴ μ ∗ d = I
4. 线性筛法求莫比乌斯函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 void Mobius (int n) { mu[1 ] = 1 ; for (int i=2 ;i<=n;++i){ if (!st[i]) p[++cnt] = i, mu[i] = -1 ; for (int j=1 ;p[j]<=n/i;++j){ st[p[j] * i] = true ; if (i % p[j] == 0 ) break ; mu[p[j] * i] = -mu[i]; } } }
莫比乌斯反演
莫反的函数定义和转换过程大多依靠平时积累,见过类似套路,就会,没见过,就寄。——yxc
1. 定义
设f ( n ) f(n) f ( n ) 为数论函数(定义在正整数集合上的函数)
因数形式:
F ( n ) = f ∗ I = ∑ d ∣ n f ( d ) ⇔ f ( n ) = ∑ d ∣ n μ ( d ) × F ( n d ) F(n)=f*I=\sum\limits_{d|n}f(d) \Leftrightarrow f(n)=\sum\limits _{d|n}\mu(d)\times F(\frac n d) F ( n ) = f ∗ I = d ∣ n ∑ f ( d ) ⇔ f ( n ) = d ∣ n ∑ μ ( d ) × F ( d n ) ,
证明(利用狄利克雷卷积):因为F ( n ) = f ∗ I F(n)=f*I F ( n ) = f ∗ I ,则f = F ∗ I − 1 = F ∗ μ f=F*I^{-1}=F*\mu f = F ∗ I − 1 = F ∗ μ
即f ( n ) = ∑ d ∣ n μ ( d ) × F ( n d ) f(n)=\sum\limits_{d|n}\mu(d)\times F(\frac n d) f ( n ) = d ∣ n ∑ μ ( d ) × F ( d n ) 。
证明(利用性质1+二重积分交换次序的思想):
∑ d ∣ n μ ( d ) × F ( n d ) = ∑ d ∣ n μ ( d ) × ∑ i ∣ n d f ( i ) = ∑ i ∣ n f ( i ) ∑ d ∣ n i μ ( d ) \sum\limits_{d|n}\mu(d)\times F(\frac n d)=\sum\limits_{d|n}\mu(d)\times \sum\limits_{i|\frac n d}f(i)=\sum\limits_{i|n}f(i)\sum\limits_{d|\frac n i}\mu(d) d ∣ n ∑ μ ( d ) × F ( d n ) = d ∣ n ∑ μ ( d ) × i ∣ d n ∑ f ( i ) = i ∣ n ∑ f ( i ) d ∣ i n ∑ μ ( d )
(i i i 能取到所有d d d 可以取到的取值,这样反过来看,把i i i 提到前面)
又当且仅当n = i n=i n = i 时,∑ d ∣ n i μ ( d ) = 1 \sum\limits_{d|\frac{n}i}\mu(d)=1 d ∣ i n ∑ μ ( d ) = 1 ,因此∑ d ∣ n μ ( d ) × F ( n d ) = f ( n ) \sum\limits_{d|n}\mu(d)\times F(\frac n d)=f(n) d ∣ n ∑ μ ( d ) × F ( d n ) = f ( n )
倍数形式:
F ( n ) = ∑ n ∣ N f ( N ) ⇔ f ( n ) = ∑ n ∣ N F ( N ) μ ( N n ) F(n)=\sum\limits_ {n|N}f(N) \Leftrightarrow f(n)=\sum\limits_{n|N}F(N)\mu(\frac N n) F ( n ) = n ∣ N ∑ f ( N ) ⇔ f ( n ) = n ∣ N ∑ F ( N ) μ ( n N ) ,(枚举N N N 为n n n 的所有倍数,N ∈ [ n , + ∞ ) N\in[n,+\infin) N ∈ [ n , + ∞ ) )
证明:∑ n ∣ N F ( N ) μ ( N n ) = ∑ n ∣ N μ ( N n ) ∑ N ∣ i f ( i ) \sum\limits_{n|N}F(N)\mu(\frac N n)=\sum\limits_{n|N}\mu(\frac N n)\sum\limits _{N|i}f(i) n ∣ N ∑ F ( N ) μ ( n N ) = n ∣ N ∑ μ ( n N ) N ∣ i ∑ f ( i )
设d = N n d=\frac N n d = n N ,则N = d n N=dn N = d n ,则d n ∣ i dn|i d n ∣ i ,即d ∣ i n d|\frac i n d ∣ n i
因此∑ n ∣ N μ ( N n ) ∑ N ∣ i f ( i ) = ∑ d ∣ i n μ ( d ) ∑ N ∣ i f ( i ) \sum\limits_{n|N}\mu(\frac N n)\sum\limits _{N|i}f(i)=\sum\limits_{d|\frac i n}\mu(d)\sum\limits _{N|i}f(i) n ∣ N ∑ μ ( n N ) N ∣ i ∑ f ( i ) = d ∣ n i ∑ μ ( d ) N ∣ i ∑ f ( i )
又当且仅当n = i n=i n = i 时,∑ d ∣ n i μ ( d ) = 1 \sum\limits_{d|\frac{n}i}\mu(d)=1 d ∣ i n ∑ μ ( d ) = 1 ,因此f ( n ) = ∑ n ∣ N F ( N ) μ ( N n ) f(n)=\sum\limits_{n|N}F(N)\mu(\frac N n) f ( n ) = n ∣ N ∑ F ( N ) μ ( n N )
运用莫反的时候,通常都是因为F ( n ) F(n) F ( n ) 好求,但是f ( n ) f(n) f ( n ) 不好求,因此将f ( n ) f(n) f ( n ) 用F , μ F,\mu F , μ 表示出来。
2. 应用1:莫反+整数分块
p2522 Problem b
数据范围:1 ≤ n , k ≤ 5 × 1 0 4 ; 1 ≤ a ≤ b ≤ 5 × 1 0 4 ; 1 ≤ c ≤ d ≤ 5 × 1 0 4 1\leq n,k\leq 5\times 10^4;1\leq a\leq b\leq 5\times 10^4;1\leq c \leq d \leq 5\times 10^4 1 ≤ n , k ≤ 5 × 1 0 4 ; 1 ≤ a ≤ b ≤ 5 × 1 0 4 ; 1 ≤ c ≤ d ≤ 5 × 1 0 4
思路:详细的整理一下吧。
首先,题目要我们求的东西,可以先拆成一个二维前缀和,A [ a , b ] [ c , d ] = A [ 1 , b ] [ 1 , d ] − A [ 1 , b ] [ 1 , c − 1 ] − A [ 1 , a − 1 ] [ 1 , d ] + A [ 1 , a − 1 ] [ 1 , c − 1 ] A[a,b][c,d]=A[1,b][1,d]-A[1,b][1,c-1]-A[1,a-1][1,d]+A[1,a-1][1,c-1] A [ a , b ] [ c , d ] = A [ 1 , b ] [ 1 , d ] − A [ 1 , b ] [ 1 , c − 1 ] − A [ 1 , a − 1 ] [ 1 , d ] + A [ 1 , a − 1 ] [ 1 , c − 1 ] 。
设f ( k ) = ∑ x = 1 a ∑ y = 1 b [ ( x , y ) = k ] f(k)=\sum\limits _{x=1}^a\sum\limits _{y=1}^b[(x,y)=k] f ( k ) = x = 1 ∑ a y = 1 ∑ b [ ( x , y ) = k ] ,然后我们方便求的是这个F ( k ) = ∑ x = 1 a ∑ y = 1 b [ k ∣ ( x , y ) ] F(k)=\sum\limits _{x=1}^a\sum\limits _{y=1}^b[k|(x,y)] F ( k ) = x = 1 ∑ a y = 1 ∑ b [ k ∣ ( x , y ) ] ,且F ( k ) = ∑ k ∣ N f ( N ) F(k)=\sum\limits _{k|N}f(N) F ( k ) = k ∣ N ∑ f ( N )
则代入莫反倍数形式得f ( k ) = ∑ k ∣ N μ ( N k ) F ( N ) f(k)=\sum\limits _{k|N}\mu(\frac N k) F(N) f ( k ) = k ∣ N ∑ μ ( k N ) F ( N )
先求F ( N ) F(N) F ( N ) 。首先,N ∣ ( x , y ) N|(x,y) N ∣ ( x , y ) ,也就是说,N ∣ x , N ∣ y N|x,N|y N ∣ x , N ∣ y ,因此所有满足条件的点对数量为⌊ a N ⌋ × ⌊ b N ⌋ \lfloor \frac a N \rfloor\times \lfloor \frac b N \rfloor ⌊ N a ⌋ × ⌊ N b ⌋
则f ( k ) = ∑ k ∣ N μ ( N k ) ⌊ a k ⌋ × ⌊ b k ⌋ f(k)=\sum\limits _{k|N}\mu(\frac N k)\lfloor \frac a k \rfloor\times \lfloor \frac b k \rfloor f ( k ) = k ∣ N ∑ μ ( k N ) ⌊ k a ⌋ × ⌊ k b ⌋ ,设 t = N k t=\frac{N}{k} t = k N ,显然枚举 t t t 的结果为1 , 2 , . . , 1,2,.., 1 , 2 , . . , 这样的整数,N = t k N=tk N = t k 。
f ( k ) = ∑ t μ ( t ) ⌊ a t k ⌋ × ⌊ b t k ⌋ f(k)=\sum\limits_{t}\mu(t)\lfloor \frac a {tk} \rfloor\times \lfloor \frac b {tk} \rfloor f ( k ) = t ∑ μ ( t ) ⌊ t k a ⌋ × ⌊ t k b ⌋ ,再运用整数分块的知识进行求解即可,注释都写在代码里吧。
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 #include <bits/stdc++.h> using namespace std;#define ll long long typedef pair<int , int > pii;typedef pair<ll,ll> pll;#define xx first #define yy second #define ls (oo << 1) #define rs (oo << 1 | 1) #define PI acos(-1.0) ll read (void ) ;int n, cnt;const int N = 5e4 + 5 ; int p[N], mu[N];int pre[N];bool st[N];void Mobius (int n) { mu[1 ] = 1 ; for (int i=2 ;i<=n;++i){ if (!st[i]) p[++cnt] = i, mu[i] = -1 ; for (int j=1 ;p[j]<=n/i;++j){ st[p[j] * i] = true ; if (i % p[j] == 0 ) break ; mu[p[j] * i] = -mu[i]; } } for (int i=1 ;i<=n;++i){ pre[i] = pre[i - 1 ] + mu[i]; } } ll f (int a, int b, int k) { a /= k, b /= k; ll res = 0 , n = min (a, b), l = 1 , r; while (l <= n){ r = min (n, min (a / (a / l), b / (b / l))); res += 1LL * (pre[r] - pre[l - 1 ]) * (a / l) * (b / l); l = r + 1 ; } return res; } void solve () { int a, b, c, d, k; a = read (), b = read (), c = read (), d = read (), k = read (); ll res = f (b, d, k) - f (b, c - 1 , k) - f (a - 1 , d, k) + f (a - 1 , c - 1 , k); printf ("%lld\n" , res); } int main (void ) { int T; Mobius (N - 1 ); T = read (); while (T--){ solve (); } return 0 ; } ll read (void ) { ll x = 0 , f=1 ;char ch; do {ch = getchar ();if (ch == '-' ) f=-1 ;}while (ch<'0' || ch>'9' ); do {x = x*10 + (ch-'0' );ch = getchar ();}while (ch>='0' && ch<='9' ); return x*f; }
3. 应用2:莫反+提取公因数
p3327约数个数和 莫反+双分块
设d ( x ) d(x) d ( x ) 为x x x 的约数个数,给定T T T 组n , m n,m n , m ,求∑ i = 1 N ∑ j = 1 M d ( i × j ) \sum\limits _{i=1}^N \sum\limits_{j=1}^M d(i\times j) i = 1 ∑ N j = 1 ∑ M d ( i × j )
数据范围:1 ≤ N , M , T ≤ 5 × 1 0 4 1\leq N,M,T\leq 5\times 10^4 1 ≤ N , M , T ≤ 5 × 1 0 4
∑ i = 1 N ∑ j = 1 M d ( i × j ) = ∑ i = 1 N ∑ j = 1 M ∑ x ∣ i ∑ y ∣ j [ ( x , y ) = 1 ] \sum\limits _{i=1}^N \sum\limits_{j=1}^M d(i\times j)=\sum\limits _{i=1}^N \sum\limits_{j=1}^M \sum\limits _{x|i} \sum\limits_{y|j} [(x,y)=1] i = 1 ∑ N j = 1 ∑ M d ( i × j ) = i = 1 ∑ N j = 1 ∑ M x ∣ i ∑ y ∣ j ∑ [ ( x , y ) = 1 ]
证明 :设i = ∏ i = 1 k p i a i , j = ∏ i = 1 k p i b i i=\prod_{i=1}^k p_i^{a_i},j=\prod_{i=1}^k p_i^{b_i} i = ∏ i = 1 k p i a i , j = ∏ i = 1 k p i b i ,0 ≤ a i , b i 0\leq a_i,b_i 0 ≤ a i , b i
则i × j = ∏ i = 1 k p i a i + b i i\times j=\prod_{i=1}^k p_i^{a_i+b_i} i × j = ∏ i = 1 k p i a i + b i ,d ( i × j ) = ∏ i = 1 k ( a i + b i + 1 ) d(i\times j)=\prod_{i=1}^k(a_i+b_i+1) d ( i × j ) = ∏ i = 1 k ( a i + b i + 1 )
即从i i i 中选出约数x x x ,j j j 中选出约数y y y ,对于p 1 p_1 p 1 而言,若要求( x , y ) = 1 (x,y)=1 ( x , y ) = 1
则可以x = 1 , y = 1 x=1,y=1 x = 1 , y = 1 ,或者x = 1 , y = ∈ [ p 1 , p 1 b 1 ] x=1,y=\in[p_1,p_1^{b_1}] x = 1 , y = ∈ [ p 1 , p 1 b 1 ] ,或者x ∈ [ p 1 , p 1 a 1 ] , y = 1 x\in[p_1,p_1^{a_1}],y=1 x ∈ [ p 1 , p 1 a 1 ] , y = 1
一共是( a 1 + b 1 + 1 ) (a_1+b_1+1) ( a 1 + b 1 + 1 ) 种取法,其他质数同理。根据乘法原理,这些取法正好就是d ( i × j ) d(i\times j) d ( i × j ) 。
设出f ( n ) , F ( n ) f(n),F(n) f ( n ) , F ( n ) 。
设f ( n ) = ∑ i = 1 N ∑ j = 1 M ∑ x ∣ i ∑ y ∣ j [ ( x , y ) = n ] f(n)=\sum\limits _{i=1}^N \sum\limits_{j=1}^M \sum\limits _{x|i} \sum\limits_{y|j} [(x,y)=n] f ( n ) = i = 1 ∑ N j = 1 ∑ M x ∣ i ∑ y ∣ j ∑ [ ( x , y ) = n ] ,显然f ( 1 ) f(1) f ( 1 ) 就是答案。
设F ( n ) = ∑ i = 1 N ∑ j = 1 M ∑ x ∣ i ∑ y ∣ j [ n ∣ ( x , y ) ] F(n)=\sum\limits _{i=1}^N \sum\limits_{j=1}^M \sum\limits _{x|i} \sum\limits_{y|j} [n|(x,y)] F ( n ) = i = 1 ∑ N j = 1 ∑ M x ∣ i ∑ y ∣ j ∑ [ n ∣ ( x , y ) ] ,则F ( n ) = ∑ n ∣ d f ( d ) F(n)=\sum\limits _{n|d}f(d) F ( n ) = n ∣ d ∑ f ( d )
即f ( n ) = ∑ n ∣ d μ ( d n ) F ( d ) f(n)=\sum\limits _{n|d}\mu(\frac d n)F(d) f ( n ) = n ∣ d ∑ μ ( n d ) F ( d ) 令T = min ( N , M ) T=\min(N,M) T = min ( N , M ) ,则f ( 1 ) = ∑ d = 1 T μ ( d ) F ( d ) f(1)=\sum\limits _{d=1}^T\mu(d)F(d) f ( 1 ) = d = 1 ∑ T μ ( d ) F ( d ) 。
F ( n ) = ∑ i = 1 N ∑ j = 1 M ∑ x ∣ i ∑ y ∣ j [ n ∣ ( x , y ) ] = ∑ x = 1 N ∑ y = 1 M ⌊ N x ⌋ ⌊ M y ⌋ [ n ∣ ( x , y ) ] F(n)=\sum\limits _{i=1}^N \sum\limits_{j=1}^M \sum\limits _{x|i} \sum\limits_{y|j} [n|(x,y)]=\sum\limits _{x=1}^N \sum\limits_{y=1}^M \lfloor \frac N x \rfloor \lfloor \frac M y \rfloor [n|(x,y)] F ( n ) = i = 1 ∑ N j = 1 ∑ M x ∣ i ∑ y ∣ j ∑ [ n ∣ ( x , y ) ] = x = 1 ∑ N y = 1 ∑ M ⌊ x N ⌋ ⌊ y M ⌋ [ n ∣ ( x , y ) ]
证明 :首先,x ∣ i , y ∣ j x|i,y|j x ∣ i , y ∣ j ,那么x , y x,y x , y 肯定是能取到[ 1 , N ] , [ 1 , M ] [1,N],[1,M] [ 1 , N ] , [ 1 , M ] 的。当x , y x,y x , y 固定后,[ n ∣ ( x , y ) ] [n|(x,y)] [ n ∣ ( x , y ) ] 和i , j i,j i , j 是没有关系的,我们可以把它提出来。那么,里面就变成了∑ i = 1 ⌊ N x ⌋ ∑ j = 1 ⌊ M y ⌋ 1 \sum\limits _{i=1}^{\lfloor \frac N x \rfloor}\sum\limits _{j=1}^{\lfloor \frac M y \rfloor}1 i = 1 ∑ ⌊ x N ⌋ j = 1 ∑ ⌊ y M ⌋ 1 ,也就是N , M N,M N , M 里面有多少个i , j i,j i , j ,它们是x , y x,y x , y 的倍数,得证。
下面再消掉[ n ∣ ( x , y ) ] [n|(x,y)] [ n ∣ ( x , y ) ] 这个条件。
设x ′ = ⌊ x n ⌋ , y ′ = ⌊ y n ⌋ x'=\lfloor \frac x n \rfloor,y'=\lfloor \frac y n \rfloor x ′ = ⌊ n x ⌋ , y ′ = ⌊ n y ⌋
F ( n ) = ∑ x = 1 N ∑ y = 1 M ⌊ N x ⌋ ⌊ M y ⌋ [ n ∣ ( x , y ) ] = ∑ x ′ = 1 ⌊ N n ⌋ ∑ y ′ = 1 ⌊ M n ⌋ ⌊ N n x ′ ⌋ ⌊ M n y ′ ⌋ F(n)=\sum\limits _{x=1}^N \sum\limits_{y=1}^M \lfloor \frac N x \rfloor \lfloor \frac M y \rfloor [n|(x,y)]=\sum\limits _{x'=1}^{\lfloor \frac N n \rfloor}\sum\limits _{y'=1}^{\lfloor \frac M n \rfloor}\lfloor \frac N {nx'} \rfloor\lfloor \frac M {ny'} \rfloor F ( n ) = x = 1 ∑ N y = 1 ∑ M ⌊ x N ⌋ ⌊ y M ⌋ [ n ∣ ( x , y ) ] = x ′ = 1 ∑ ⌊ n N ⌋ y ′ = 1 ∑ ⌊ n M ⌋ ⌊ n x ′ N ⌋ ⌊ n y ′ M ⌋
令N ′ = ⌊ N n ⌋ , M ′ = ⌊ M n ⌋ N'=\lfloor \frac N n \rfloor,M'=\lfloor \frac M n \rfloor N ′ = ⌊ n N ⌋ , M ′ = ⌊ n M ⌋
F ( n ) = ∑ x ′ = 1 N ′ ∑ y ′ = 1 M ′ ⌊ N ′ x ′ ⌋ ⌊ M ′ y ′ ⌋ = ( ∑ x ′ = 1 N ′ ⌊ N ′ x ′ ⌋ ) × ( ∑ y ′ = 1 M ′ ⌊ M ′ y ′ ⌋ ) F(n)=\sum\limits _{x'=1}^{N'} \sum\limits_{y'=1}^{M'} \lfloor \frac {N'} {x'} \rfloor \lfloor \frac {M'} {y'} \rfloor=(\sum\limits _{x'=1}^{N'} \lfloor \frac {N'} {x'} \rfloor)\times(\sum\limits_{y'=1}^{M'} \lfloor \frac {M'} {y'} \rfloor) F ( n ) = x ′ = 1 ∑ N ′ y ′ = 1 ∑ M ′ ⌊ x ′ N ′ ⌋ ⌊ y ′ M ′ ⌋ = ( x ′ = 1 ∑ N ′ ⌊ x ′ N ′ ⌋ ) × ( y ′ = 1 ∑ M ′ ⌊ y ′ M ′ ⌋ )
令h ( n ) = ∑ i = 1 n ⌊ n i ⌋ ) h(n)=\sum\limits_{i=1}^{n} \lfloor \frac {n} {i} \rfloor) h ( n ) = i = 1 ∑ n ⌊ i n ⌋ ) ,也就是标准整数分块,则F ( n ) = h ( N ′ ) × h ( M ′ ) F(n)=h(N')\times h(M') F ( n ) = h ( N ′ ) × h ( M ′ ) 。
f ( 1 ) = ∑ d = 1 T μ ( d ) h ( ⌊ N d ⌋ ) h ( ⌊ M d ⌋ ) f(1)=\sum\limits _{d=1}^T\mu(d)h(\lfloor \frac N d \rfloor)h(\lfloor \frac M d \rfloor) f ( 1 ) = d = 1 ∑ T μ ( d ) h ( ⌊ d N ⌋ ) h ( ⌊ d M ⌋ )
由于h ( x ) h(x) h ( x ) 只和x x x 有关,所以可以再分一次块,因此每次查询复杂度O ( N ) O(\sqrt N) O ( N ) ,总时间复杂度O ( N N ) O(N\sqrt N) O ( N 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 39 40 41 42 43 44 45 46 47 48 49 50 51 52 int cnt;const int N = 5e4 + 5 ;int p[N], h[N], pre[N], mu[N];bool st[N];void Mobius (int n) { mu[1 ] = 1 ; for (int i=2 ;i<=n;++i){ if (!st[i]) p[++cnt] = i, mu[i] = -1 ; for (int j=1 ;p[j]<=n/i;++j){ st[p[j] * i] = true ; if (i % p[j] == 0 ) break ; mu[p[j] * i] = -mu[i]; } } for (int i=1 ;i<=n;++i){ pre[i] = pre[i - 1 ] + mu[i]; } } void H (int n) { for (int i=1 ;i<=n;++i){ for (int l=1 , r;l<=i;l=r + 1 ){ r = min (i, i / (i / l)); h[i] += (r - l + 1 ) * (i / l); } } } void solve () { int n, m; n = read (), m = read (); ll res = 0 ; int k = min (n, m); for (int l=1 , r;l<=k;l=r + 1 ){ r = min (k, min (n / (n / l), m / (m / l))); res += (ll)(pre[r] - pre[l - 1 ]) * h[n / l] * h[m / l]; } printf ("%lld\n" , res); } int main (void ) { int T; Mobius (N - 1 ); H (N - 1 ); T = read (); while (T--){ solve (); } return 0 ; }
丑数筛
给定一个质数集合S = { p 1 , p 2 , . . , p k } S=\{p_1,p_2,..,p_k\} S = { p 1 , p 2 , . . , p k } ,只由这些质数相乘得到的数我们成为丑数(Humble/Ugly Numbers) 。习惯上,我们认为第一个丑数是1,但是也可能不是,所以看清题意 。记第n n n 大 的丑数为h [ n ] h[n] h [ n ] 。
无论哪种方法,这个丑数实际上都是可能非常大的,建议直接上__int128!!
第一种筛法,也是最常用的,就是搞一个优先队列,每次提出来优先队列中最小的数,注意去重 。
这种方法的时间复杂度是O ( ( n × k ) log ( n × k ) ) O((n\times k)\log (n\times k)) O ( ( n × k ) log ( n × k ) )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #define ll __int128 const ll INF = 9e37 , N = 5e4 + 5 ;ll h[N], p[3 ] = {2 , 3 , 5 }, cnt[3 ]; priority_queue<ll, vector<ll>, greater<ll> > pq; void Humble (int n) { pq.push (1 ); ll last = 1 ; for (int i=1 ;i<=n;++i){ for (int j=0 ;j<3 ;++j){ pq.push (last * p[j]); } last = pq.top (); while (!pq.empty () && last == pq.top ()) pq.pop (); h[i] = last; } }
第二种线性筛法,相当于做了一个DP+双指针,复杂度O ( n × k ) O(n\times k) O ( n × k ) 。注意,这里的下标是从0开始的,并且,INF要足够足够大!!!!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ll h[N], p[3 ] = {2 , 3 , 5 }, cnt[3 ]; void Humble (int n) { h[0 ] = 1 ; for (int i=1 ;i<=n;++i){ h[i] = INF; for (int j=0 ;j<3 ;++j){ while (p[j] * h[cnt[j]] <= h[i - 1 ]) cnt[j]++; if (p[j] * h[cnt[j]] < h[i]){ h[i] = p[j] * h[cnt[j]]; } } } }
快速幂&快速乘
快速幂
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. 欧拉降幂
运用基础篇欧拉定理推论1公式: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 ) ,即可。因为经常用到高精度,所以在这里放一个Python的代码吧
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 """ Created on Mon Apr 11 14:47:12 2022 @ 求x^(n-1) % mod @author: KZ """ def Eulers (x ): res = x for i in range (2 , x): if i > x // i: break if x % i == 0 : res = res // i * (i - 1 ) while x % i == 0 : x = x // i if x > 1 : res = res // x * (x - 1 ) return res def qmi (a, b, p ): res = 1 % p while (b): if (b & 1 == 1 ): res = res * a % p a = a * a % p b = b >> 1 return res arr = input ().split() x = int (arr[0 ]) n = int (arr[1 ]) mod = int (arr[2 ]) if mod == 1 : print (0 ) elif n == 1 : print (1 ) else : M = Eulers(mod) b = (n - 1 ) % M res = qmi(x, b, mod) print (res)
3. 同一底数和同一模数的预处理快速幂
前置知识:数论分块;数学基础篇相关定理
4. 十进制快速幂
当二进制快速幂都超时的时候,可以尝试用下十进制快速幂。
比如,3 405 = ( 3 1 ) 5 × ( 3 10 ) 0 + ( 3 100 ) 4 3^{405}=(3^1)^5\times(3^{10})^0+(3^{100})^4 3 4 0 5 = ( 3 1 ) 5 × ( 3 1 0 ) 0 + ( 3 1 0 0 ) 4
感觉不太能用到的东西(欧拉降幂和python高精都可以解决吧应该),随便贴个代码参考
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 #include <iostream> #include <cstdio> #include <cstring> using namespace std;#define ri register int #define ll long long ll b,t,h,P; char c[ 100007 ];ll TenthPow ( ll a ) { ll ans = 1 ,s = a; while ( t >= 0 ){ ll cnt = c[ t ] - '0' ,cur = s; for ( ri i = 1 ; i <= cnt ; ++i ) ans = ans * s % P; for ( ri i = 1 ; i < 10 ; ++i ) cur = cur * s % P; s = cur; ans %= P; --t; } return ans; } int main () { cin >> b >> c >> P; t = strlen ( c ); --t; cout << b << "^" << c << " mod " << P << "=" << TenthPow ( b ); return 0 ; }
快速乘
1. 虚假的快速乘 复杂度O ( log b ) O(\log b) O ( log b )
1 2 3 4 5 6 7 8 9 10 ll qmul (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. 真正的快速乘 复杂度O ( 1 ) O(1) O ( 1 )
应用范围 :计算a × b m o d m ; a , b ≤ m ≤ 1 0 18 a\times b \bmod m;a,b\leq m\leq 10^{18} a × b m o d m ; a , b ≤ m ≤ 1 0 1 8
等,等一下,a , b ≤ 1 0 18 a,b\leq 10^{18} a , b ≤ 1 0 1 8 ,但是_ _ i n t 128 \_\_int128 _ _ i n t 1 2 8 极限可以处理1 0 37 10^{37} 1 0 3 7 这么大的数,为什么还要快速乘?只有在数组空间开不下的很苛刻的情况下才能用得上吧。那这里就截图贴一下推导过程和代码。
1 2 3 4 5 6 7 8 9 10 #define ull unsigned long long #define ll long long #define ld long double ll binmul (ll a, ll b, ll m) { ull c = (ull)a * b - (ull)((ld)a / m * b + 0.5L ) * m; if (c < m) return c; return c + m; }
不得不说的那些题
拆数+数学思维 102780J Something that resembles Waring’s problem
给定N N N ,要求构造出x 1 3 + x 2 3 + . . . + x k 3 = N x_1^3+x_2^3+...+x_k^3=N x 1 3 + x 2 3 + . . . + x k 3 = N ,k ≤ 5 k\leq 5 k ≤ 5
数据范围:1 ≤ N ≤ 1 0 100000 ; ∣ x i ∣ ≤ 1 0 110000 1\leq N\leq 10^{100000};|x_i|\leq 10^{110000} 1 ≤ N ≤ 1 0 1 0 0 0 0 0 ; ∣ x i ∣ ≤ 1 0 1 1 0 0 0 0
这道题,应当通过立方和之间的加减,消去3次和2次项,尝试构造出一次项。通过神秘的东方力量 日常的积累,也许可以蒙出来 想到( x − 1 ) 3 + ( x + 1 ) 3 − x 3 − x 3 = 6 x (x-1)^3+(x+1)^3-x^3-x^3=6x ( x − 1 ) 3 + ( x + 1 ) 3 − x 3 − x 3 = 6 x
认真的说,这个更像完全平方公式的拓展:( x + 1 ) 2 − ( x − 1 ) 2 − x 2 − x 2 = 4 x (x+1)^2-(x-1)^2-x^2-x^2=4x ( x + 1 ) 2 − ( x − 1 ) 2 − x 2 − x 2 = 4 x
总之,我们可以通过这个公式,构造出一个6 6 6 的倍数,然后让这个数加某一个t 3 t^3 t 3 ,使得它变成N N N 。
也就是说,N = 6 x + t 3 N=6x+t^3 N = 6 x + t 3 。进一步的,我们发现这样一个神奇性质:
1 3 m o d 6 = 1 ; 2 3 m o d 6 = 2 ; 3 3 m o d 6 = 3 ; 4 3 m o d 6 = 4 ; 5 3 m o d 6 = 5 ; 1^3\bmod 6=1;2^3\bmod 6=2;3^3\bmod 6=3;4^3\bmod 6=4;5^3\bmod 6=5; 1 3 m o d 6 = 1 ; 2 3 m o d 6 = 2 ; 3 3 m o d 6 = 3 ; 4 3 m o d 6 = 4 ; 5 3 m o d 6 = 5 ;
因此我们发现,令t = N m o d 6 t=N \bmod 6 t = N m o d 6 ,让a = ⌊ N 6 ⌋ − ⌊ t 6 ⌋ a=\lfloor \frac N 6\rfloor-\lfloor \frac t 6\rfloor a = ⌊ 6 N ⌋ − ⌊ 6 t ⌋
则N = 6 × ⌊ N 6 ⌋ + N m o d 6 = 6 x + t m o d 6 = 6 x − 6 ⌊ t 6 ⌋ + t 3 = 6 a + t 3 N=6\times \lfloor \frac N 6\rfloor+N\bmod 6=6x+t\bmod 6=6x-6\lfloor \frac t 6\rfloor+t^3=6a+t^3 N = 6 × ⌊ 6 N ⌋ + N m o d 6 = 6 x + t m o d 6 = 6 x − 6 ⌊ 6 t ⌋ + t 3 = 6 a + t 3 。
Python代码如下:
1 2 3 4 5 n = int (input ()) t = n % 6 a = (n // 6 ) - (t**3 ) // 6 print (5 )print ("{0} {1} {2} {3} {4}" .format (a-1 , a+1 , -a, -a, t))
数学+单调栈 102483A Access Points
给定n n n 个点,可以改变它们的坐标位置,对于每一个点( x , y ) (x,y) ( x , y ) ,将( x , y ) (x,y) ( x , y ) 改变为( x ′ , y ′ ) (x',y') ( x ′ , y ′ ) 的花费为( x ′ − x ) 2 + ( y ′ − y ) 2 (x'-x)^2+(y'-y)^2 ( x ′ − x ) 2 + ( y ′ − y ) 2 。经过若干次改变之后,它们必须要满足∀ i < j \forall i<j ∀ i < j ,点i , j i,j i , j 的坐标分别为( x i , y i ) , ( x j , y j ) (x_i,y_i),(x_j,y_j) ( x i , y i ) , ( x j , y j ) ,x i ≤ x j , y i ≤ y j x_i\leq x_j,y_i\leq y_j x i ≤ x j , y i ≤ y j 。问最小的总花费之和为多少。
数据范围:1 ≤ n ≤ 1 0 5 1\leq n\leq 10^5 1 ≤ n ≤ 1 0 5 ,每个点坐标( s i , t i ) (s_i,t_i) ( s i , t i ) ,1 ≤ s i , t i ≤ 1 0 6 1\leq s_i,t_i\leq 10^6 1 ≤ s i , t i ≤ 1 0 6 。
首先,x , y x,y x , y 方向的花费其实是相互独立的,那么答案就是∑ i = 1 n ( x ′ − x ) 2 \sum\limits _{i=1} ^n (x'-x)^2 i = 1 ∑ n ( x ′ − x ) 2 与∑ i = 1 n ( y ′ − y ) 2 \sum\limits _{i=1} ^n (y'-y)^2 i = 1 ∑ n ( y ′ − y ) 2 之和,独立计算。
问题转化为,对于一个数组a [ n ] a[n] a [ n ] ,可以对所有的a i a_i a i 执行加或减操作,变成p i p_i p i ,求让p [ n ] p[n] p [ n ] 单调非递减的最小花费。首先,如果a [ n ] a[n] a [ n ] 本身就是单调非递减的,那么不需要进行任何操作,∀ i ∈ [ 1 , n ] \forall i\in[1,n] ∀ i ∈ [ 1 , n ] ,p [ i ] = a [ i ] p[i]=a[i] p [ i ] = a [ i ] ,总花费为0。如果a [ n ] a[n] a [ n ] 不满足该规律,可能有增有减,那么可以考虑把a [ n ] a[n] a [ n ] 分割为若干个串,对于每个串,都将这一个串中的数变成它们的均值,再处理下一个串,使得下一个串的平均值大于上一个串的平均值。也就是每个串的均值递增,串内部的所有数都变为相等。
首先,这样显然是可以构造出一个非递减序列的。然后我们来证明它可以得到最小花费。
如果某个a i ≤ a i + 1 a_i\leq a_{i+1} a i ≤ a i + 1 ,它是不需要操作的,只有当a i > a i + 1 a_i> a_{i+1} a i > a i + 1 的时候才需要操作。那么,让p [ i ] = p [ i + 1 ] p[i]=p[i+1] p [ i ] = p [ i + 1 ] 的花费,一定比让p [ i ] < p [ i + 1 ] p[i]<p[i+1] p [ i ] < p [ i + 1 ] 的花费要更小。(邻项微扰法就可以反证出)那么对于某个值,它要么会和前面若干个数合并,要么会和后面若干个数合并(这个值本身就合法时也可以保持自己一人一组)。如果它的值比前面的p [ i − 1 ] p[i-1] p [ i − 1 ] 所属组的均值要小或者相等,那么它应该归到p [ i − 1 ] p[i-1] p [ i − 1 ] 组,因为规定了p [ i + 1 ] p[i+1] p [ i + 1 ] 组的均值大于p [ i − 1 ] p[i-1] p [ i − 1 ] 。否则,应该归到p [ i + 1 ] p[i+1] p [ i + 1 ] 组(因为这不会带来花费)。用一个单调栈就可以很方便的处理。
and一组数变为一个值的时候,为什么是变成均值最小呢?这是初中数学啊QAQ
让我们回顾一下最小二乘法ϵ = ∑ ( y − y i ) 2 \epsilon=\sum(y-y_i)^2 ϵ = ∑ ( y − y i ) 2 ,ϵ \epsilon ϵ 最小时,取到真值。对这个二次函数求导
d d y ϵ = d d y ∑ ( y − y i ) 2 = 2 ∑ ( y − y i ) = 2 ( n × y − ∑ y i ) = 0 \frac{d}{dy}\epsilon=\frac{d}{dy}\sum(y-y_i)^2=2\sum(y-y_i)=2(n\times y-\sum y_i)=0 d y d ϵ = d y d ∑ ( y − y i ) 2 = 2 ∑ ( y − y i ) = 2 ( n × y − ∑ y i ) = 0
显然当y = ∑ y i n y=\frac{\sum y_i}{n} y = n ∑ y 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 int n, tt;const int N = 1e5 + 10 ;ll x[N], y[N]; pll s[N]; double ans;double cal (ll a[]) { tt = 0 , ans = 0 ; for (int i=1 ;i<=n;++i){ ll cur = a[i], le = 1 ; while (tt && s[tt].xx*le>=s[tt].yy*cur){ le += s[tt].yy, cur += s[tt].xx; --tt; } s[++tt] = pll (cur, le); } int inx = n; while (tt){ double tmp = 1.0 *s[tt].xx/s[tt].yy; ll le = s[tt].yy; --tt; for (int j=0 ;j<le;++j){ ans += (tmp-a[inx-j]) * (tmp-a[inx-j]); } inx -= le; } return ans; } int main (void ) { n = read (); for (int i=1 ;i<=n;++i){ x[i] = read (), y[i] = read (); } printf ("%.9lf\n" , cal (x)+cal (y)); }
概率DP+状压 103447G Damaged Bicycle
你需要从1 走到n ,初始速度是t,某些地方有自行车,每个位置自行车有pi的概率是坏掉的,如果自行车没坏可以骑上自行车,速度是r,可以一直骑着到终点。
懒得写题解了0.0,记录下Dijstra+状压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 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 int n, m, t, r, k;const int N = 1e5 + 5 , M = 20 + 5 , K = 1e6 + 5 ;const int INF = 2e9 + 10 ;int dis[M][N], cnt[N], b[M], id[N];bool vis[M][N];double f[M][K], p[M];vector<pii> ve[N]; priority_queue<pii, vector<pii>, greater<pii> > pq; void Dijkstra (int id, int u) { for (int i=1 ;i<=n;++i) dis[id][i] = INF; pq.push (pii (0 , u)); dis[id][u] = 0 ; while (!pq.empty ()){ pii tmp = pq.top (); pq.pop (); int v = tmp.yy, w = tmp.xx; if (vis[id][v]) continue ; vis[id][v] = true ; dis[id][v] = w; for (auto i : ve[v]){ if (dis[id][i.yy] > dis[id][v] + i.xx){ dis[id][i.yy] = dis[id][v] + i.xx; pq.push (pii (dis[id][i.yy], i.yy)); } } } } double dp (int st, int u) { if (f[u][st] > 0 ) return f[u][st]; double time = p[u] * dis[u][n] / t + (1.0 - p[u]) * dis[u][n] / r; for (int i=0 ;i<k;++i){ if (st >> i & 1 ) continue ; time = min (time, p[u] * (1.0 * dis[u][b[i + 1 ]] / t + dp (st | (1 <<i), i+1 )) + (1.0 -p[u]) * dis[u][n] / r); } return f[u][st] = time; } int main (void ) { scanf ("%d%d" , &t, &r); scanf ("%d%d" , &n, &m); int u, v, w, cp; for (int i=1 ;i<=m;++i){ scanf ("%d%d%d" ,&u, &v, &w); ve[u].push_back (pii (w, v));ve[v].push_back (pii (w, u)); } scanf ("%d" , &k); for (int i=1 ;i<=k;++i){ scanf ("%d%d" , &b[i], &cp); p[i] = cp / 100.0 , id[b[i]] = i; Dijkstra (i, b[i]); } Dijkstra (k + 1 , 1 ), Dijkstra (k + 2 , n); if (dis[k+1 ][n] >= INF){ puts ("-1" ); return 0 ; } p[k + 1 ] = 1.0 ; memset (f, -1 , sizeof (f)); dp (0 , k + 1 ); printf ("%.9lf\n" , f[k + 1 ][0 ]); return 0 ; }
递归+DP 103366 F Four Column Hanoi Tower
四层汉诺塔问题,实际上有有一个诡异的O ( 1 ) O(1) O ( 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 76 77 78 79 80 81 82 83 84 85 86 #include <bits/stdc++.h> using namespace std;#define ll long long int n;int base = 10 ;const int N = 1e4 + 10 ;vector<int > p2[N], f[N]; vector<int > Min (vector<int > A, vector<int > B) { if (A.size () != B.size ()) return (A.size () < B.size ())?(A):(B); for (int i=A.size ()-1 ;i>=0 ;--i){ if (A[i] != B[i]) return (A[i] < B[i])?(A):(B); } return A; } vector<int > add (vector<int > A, vector<int > B) { vector<int > res; if (A.size ()<B.size ()){ return add (B,A); } int t = 0 ; for (int i=0 ;i<A.size ();++i){ t += A[i]; if (i<B.size ()){ t += B[i]; } res.push_back (t%base); t /= base; } if (t){ res.push_back (1 ); } return res; } vector<int > mul (vector<int > &A, int b) { vector<int > res; int t = 0 ; for (int i=0 ;i<A.size () | t;++i){ if (i<A.size ()) t += A[i]*b; res.push_back (t % base); t /= base; } while (res.size ()>1 && res.back ()==0 ){ res.pop_back (); } return res; } void init () { p2[0 ].push_back (0 ); vector<int > one; one.push_back (1 ); for (int i=1 ;i<200 ;++i){ p2[i] = add (mul (p2[i-1 ], 2 ), one); } f[1 ].push_back (1 ); f[2 ].push_back (3 ); for (int i=3 ;i<=10000 ;++i){ f[i] = p2[180 ]; for (int j=1 ;j<min (150 , i);++j){ f[i] = Min (f[i], add (mul (f[i-j], 2 ), p2[j]) ); } } } void solve () { scanf ("%d" , &n); for (int i=f[n].size ()-1 ;i>=0 ;--i){ printf (i==0 ?"%d\n" :"%d" , f[n][i]); } } int main () { int t; scanf ("%d" , &t); init (); while (t--){ solve (); } return 0 ; }