zhx好闪,拜谢zhx
本人LaTeX存在问题,下课修改(
Day 1
上午
电脑1s跑3e8()
mod
a / b = c . . . d , a = b ⋅ c + d a / b = c...d , a = b \cdot c + d a/b=c...d,a=b⋅c+d
b > d ≥ 0 b > d \ge 0 b>d≥0
c = a / b , d = a m o d b c = a / b , d = a \bmod b c=a/b,d=amodb
( a + b ) m o d c = ( a m o d c + b m o d c ) m o d c (a + b) \bmod c = (a \bmod c + b \mod c) \bmod c (a+b)modc=(amodc+bmodc)modc
( a − b ) m o d c = ( a m o d c − b m o d c ) m o d c (a - b) \bmod c = (a \bmod c - b \bmod c) \bmod c (a−b)modc=(amodc−bmodc)modc
( a ⋅ b ) m o d c = ( a m o d c ) ⋅ ( b m o d c ) m o d c (a \cdot b) \bmod c = (a \bmod c) \cdot (b \bmod c) \bmod c (a⋅b)modc=(amodc)⋅(bmodc)modc
例1
读入n, p,输出n! % p
2 <= p <= 1e9, 1 <= n <= 1000;
ll n = read(), p = read();
ll ans = 1;
if(n >= p){
cout << 0;
return 0;
}
for(int i = 2; i <= n; ++i)ans = (ans * i) % p;
cout << ans % p;
gcd & lcm
设 g = gcd ( a , b ) , a = a ′ g , b = b ′ g g = \gcd(a, b), a = a'g, b = b'g g=gcd(a,b),a=a′g,b=b′g
∴ gcd ( a ′ , b ′ ) = 1 \therefore \gcd(a', b') = 1 ∴gcd(a′,b′)=1
∴ gcd ( a − b , b ) = gcd ( ( a ′ − b ′ ) g , b ′ g ) = gcd ( a ′ − b ′ , b ′ ) ⋅ g = 1 ⋅ g = g \therefore \gcd(a - b, b) = \gcd((a' - b')g, b'g) = \gcd(a' - b', b') \cdot g = 1 \cdot g = g ∴gcd(a−b,b)=gcd((a′−b′)g,b′g)=gcd(a′−b′,b′)⋅g=1⋅g=g
gcd ( a , b ) = gcd ( a − b , b ) = gcd ( a − 2 b , b ) = . . . . . = gcd ( a m o d b , b ) \gcd(a, b) = \gcd(a - b, b) = \gcd(a - 2b, b) = ..... = \gcd(a \bmod b, b) gcd(a,b)=gcd(a−b,b)=gcd(a−2b,b)=.....=gcd(amodb,b)
代码
ll gcd(ll a, ll b){return a ? gcd(b % a, a) : b;}
时间复杂度 O ( l o g n ) O(log n) O(logn)
证明:
$a \ge b, \gcd(a, b) = \gcd(b, a \bmod $ b ) b) b)
a m o d b < a / 2 a \bmod b < a / 2 amodb<a/2
证明:
分类讨论
b <= a / 2时, a % b < b <= a / 2;
a >= b > a / 2时,a % b = a - b < a / 2;
所以每次 g c d gcd gcd 相当于将 a a a 除以 2 2 2
所以 O ( log n ) O(\log n) O(logn)
例1
求gcd(a1, a2, ..., an)
int n = read(), ans;
for(int i = 1; i <= n; ++i)a[i] = read();
ans = a[1];
for(int i = 2; i <= n; ++i)ans = gcd(ans, a[i]);
cout << ans << "\n";
时间复杂度:
a n s ans ans 每次都在变小, 而每次 g c d gcd gcd 都可看做除 2 2 2
所以最多除 $ \log{ans}$ 次
a n s = a 1 ans = a1 ans=a1 , 所以 O ( n + log a 1 ) O(n + \log a_1) O(n+loga1)
设 g = gcd ( a , b ) , l = l c m ( a , b ) g = \gcd(a, b), l = lcm(a, b) g=gcd(a,b),l=lcm(a,b)
$ g \cdot l = a \cdot b$
代码
ll lcm(int a, int b){return a / gcd(a, b) * b;}
快速幂
x , y , p ≤ 1 0 9 x, y, p \le 10 ^ 9 x,y,p≤109
例: y = 37 y = 37 y=37 时
x 37 = ( x 18 ) 2 ⋅ x x ^ {37} = (x ^ {18}) ^ 2 \cdot x x37=(x18)2⋅x
x 18 = ( x 9 ) 2 x ^ {18} = (x ^ 9) ^ 2 x18=(x9)2
x 9 = ( x 4 ) 2 ⋅ x x ^ 9 = (x ^ 4) ^ 2 \cdot x x9=(x4)2⋅x
x 4 = ( x 2 ) 2 x ^ 4 = (x ^ 2) ^ 2 x4=(x2)2
x 2 = x ⋅ x x ^ 2 = x \cdot x x2=x⋅x
时间复杂度 O ( log y ) O(\log y) O(logy)
代码
通用版(循环)
ll qpow(ll a, ll b, ll p){
if(y == 0)return 1;
ll res = 1;
while(b){
if(b & 1)res = res * a % p;
b >>= 1;
a = a * a % p;
}
return res;
}
zhx版(递归)
ll ksm(ll x, ll y, ll p){
if(y == 0)return 1;
ll z = ksm(x, y >> 2, p);
return z * z * (y & 1 ? x : 1) % p
}
例1
x, y, p <= 10 ^ 18
求x * y % p
直接求x * y达到10 ^ 36存不下
例 y = 37 用快速幂思想
x * y = x * 37 = x + x + .. = x
x * 37 = x * 18 + x * 18 + x
x * 18 = x * 9 + x * 9
x * 9 = x * 4 + x * 4 + x
x * 4 = x * 2 + x * 2
x * 2 = x + x
代码
ll ksc(ll x, ll y, ll p){
if(y == 0)return 0;
if(y == 1)return x;
ll z = ksc(x, y >> 2, p);
return (z + z + (y & 1 ? x : 0)) % p
}
矩阵
大抵可以看做一个二维数组
被迫使用LaTeX(
矩阵加法与减法
$$
\begin{vmatrix}{}
1 & 2 & 3 \
3 & 2 & 1
\end{vmatrix}
\begin{vmatrix}{}
1 & 2 & 3 \
3 & 2 & 1
\end{vmatrix}
=
\begin{vmatrix}{}
4 & 4 & 4 \
4 & 4 & 4
\end{vmatrix}
$$
矩阵减法同理
矩阵乘法
A ( n ⋅ m ) ⋅ B ( m ⋅ k ) A(n \cdot m) \cdot B(m \cdot k) A(n⋅m)⋅B(m⋅k)
只有第一个矩阵的列数等于第二个矩阵的行数才行
$$
\begin{vmatrix}{}
1 & 2 & 3 \
3 & 2 & 1
\end{vmatrix}
\times
\begin{vmatrix}{}
1 & 2 \
1 & 2 \
1 & 2
\end{vmatrix}
=
\begin{vmatrix}{}
1 * 1 + 2 * 1 + 3 * 1 & 1 * 2 + 2 * 2 + 3 * 2 \
3 * 1 + 2 * 1 + 1 * 1 & 3 * 2 + 2 * 2 + 1 * 2
\end{vmatrix}
=
\begin{vmatrix}{}
6 & 12 \
6 & 12
\end{vmatrix}
$$
代码:
struct Matrix{
ll a[105][105];//自行开空间
int n, m;
Matrix() {
memset(a, 0, sizeof a);}
Matrix operator * (const Matrix &A, const Matrix &B){
//& 直接调用A, B.const 防止 A, B 被修改
Matrix Ans;
int n = A.n, m = B.m;
Ans.n = n, Ans.m = m;
for(int i =1; i <= n; ++i)
for(int j = 1; j <= m; ++j)
for(int k = 1; k <= A.m; ++k)
Ans.a[i][j] += A.a[i][k] * B.a[k][j];
return Ans;
}
};
时间复杂度 O ( n ⋅ m 2 ) O(n \cdot m ^ 2) O(n⋅m2)
i , j , k i, j, k i,j,k 枚举顺序修改对结果无影响,但对速度有影响
i , j , k i,j,k i,j,k 顺序枚举跑 n = 1024 n = 1024 n=1024 ,耗时 9.454 s 9.454s 9.454s
j , k , i j,k,i j,k,i 顺序枚举 14.19 s 14.19s 14.19s
k , j , i k,j,i k,j,i : 12.46 s 12.46s 12.46s
i , k , j i,k,j i,k,j : 5.212 s 5.212s 5.212s
所以循环顺序改成 i , k , j i, k, j i,k,j ( j j j 放在最后一位即可)
利用缓存机制
下午
例1
给定 n , p n, p n,p, 求斐波那契数列第 $ n $ 项 $ \bmod p$
数组 $ f $代表 f i b fib fib 数列
手推可得
$$
\begin{vmatrix}{}
f_{i - 1} & f_{i - 2}
\end{vmatrix}
\times
\begin{vmatrix}{}
1 & 1\
1 & 0
\end{vmatrix}
=
\begin{vmatrix}{}
f_{i} & f_{i - 1}
\end{vmatrix}
$$
令
B = ∣ 1 1 1 0 ∣ B = \begin{vmatrix}{} 1 & 1\\ 1 & 0 \end{vmatrix} B=
1110
$$
\therefore
\begin{vmatrix}{}
f_{n} & f_{n - 1}
\end{vmatrix}
=
\begin{vmatrix}{}
f_{1} & f_{0}
\end{vmatrix}
\times
B ^ n
$$
矩阵乘法套快速幂即可
#include<bits/stdc++.h>
#define ll long long
#define itn int
#define ull unsigned long long
#define gt getchar
#define pt putchar
using namespace std;
inline ll read(){
ll x=0,f=1;char ch=gt();
while(!isdigit(ch)){
if(ch=='-')f=-1;ch=gt();}
while(isdigit(ch)){
x=(x<<1)+(x<<3)+(ch^48);ch=gt();}
return x*f;}
inline void print(ll x){
if(x<0)pt('-'),x=-x;if(x>9)print(x/10);pt(x%10+48);}
const ll M = 1e9 + 7;
ll n;
struct Matrix{
ll a[105][105];//自行开空间
int n, m;
Matrix() {
memset(a, 0, sizeof a);}
}ans, base;
Matrix operator * (const Matrix &A, const Matrix &B){
//& 直接调用A, B.const 防止 A, B 被修改
Matrix Ans;
int n = A.n, m = B.m;
Ans.n = n, Ans.m = m;
for(int i =1; i <= n; ++i)
for(int j = 1; j <= m; ++j)
for(int k = 1; k <= A.m; ++k)
Ans.a[i][j] = (Ans.a[i][j] + A.a[i][k] * B.a[k][j]) % M, Ans.a[i][j] %= M;
return Ans;
}
void init(){
base.n = base.m = 2, ans.n = 2, ans.m = 2;
base.a[1][1] = base.a[1][2] = base.a[2][1] = 1;
ans.a[1][1] = ans.a[1][2] = 1;
}
void qpow(ll b){
while(b){
if(b & 1)ans = ans * base;
base = base * base;
b >>= 1;
}
}
int main(){
n = read();
if(n <= 2)return puts("1"), 0;
init();
qpow(n - 2);
cout << ans.a[1][1] % M << "\n";
return 0;
}
例2
一个数列 f i = f i − 1 × f i − 3 f_{i} = f_{i - 1} \times f_{i - 3} fi=fi−1×fi−3
仍然手推可得
$$
\begin{vmatrix}{}
f_{i - 1} & f_{i - 2} & f_{i - 3}
\end{vmatrix}
\times
\begin{vmatrix}{}
1 & 1 & 0\
0 & 0 & 1\
1 & 0 & 0
\end{vmatrix}
=
\begin{vmatrix}{}
f_{i} & f_{i - 1} & f_{i - 2}
\end{vmatrix}
$$
令
B = ∣ 1 1 0 0 0 1 1 0 0 ∣ B = \begin{vmatrix}{} 1 & 1 & 0\\ 0 & 0 & 1\\ 1 & 0 & 0 \end{vmatrix} B=
101100010
$$
\therefore
\begin{vmatrix}{}
f_{n} & f_{n - 1} & f_{n - 2}
\end{vmatrix}
=
\begin{vmatrix}{}
f_{2} & f_{1} & f_{0}
\end{vmatrix}
\times
B ^ n
$$
扩展
已知 f i = k 1 f i − 1 + k 2 f i − 2 . . . . + k m f i − m + 1 ( m ≤ i ) f_i=k_{1}f_{i-1}+k_{2}f_{i-2}....+k_{m}f_{i-m+1}(m\le i) fi=k1fi−1+k2fi−2....+kmfi−m+1(m≤i)
求 f n m o d p ( 1 ≤ n ≤ 1 0 18 ) f_n \bmod p(1\le n\le 10^{18}) fnmodp(1≤n≤1018)
构造矩阵
$$
\overbrace{
\begin{vmatrix}{}
k_1 &1&0&0&…\
k_{2}&0&1&0&…\
k_{3}&0&0&1&…\
k_{4}&0&0&0&…\
…&…&…&…&…\
k_{m}&0&0&0&…\
\end{vmatrix}}^{\text{m}}
\times
\begin{vmatrix}{}
f_1 &f_2&&f_3&…&f_m\
\end{vmatrix} ^ n
$$
即可得出答案
矩阵乘法中
A × B ≠ B × A A \times B \not = B \times A A×B=B×A
例3
一个有向图, n n n 个点,走 k k k 步, 求从 1 走到 n n n 的方案数
$ n \le 100, k \le 100$
考虑动态规划
令 b j i , j bj_{i,j} bji,j 表示 i i i 与 j j j 之间是否连边, $ f_{i,j} $ 表示走到 j j j 走了 i i i 步的方案数
所以答案即为 f k , n f_{k,n} fk,n
初始状态为 $ f_{0,1} = 1$
状态转移为 $ f_{i,j} += \sum_{k = 1}^nf_{i-1,k} \times bj_{k,i}$
部分代码:
n = read(), k = read();
for(int i = 1; i <= n; ++i)bj[i][j] = read();
f[0][1] = 1;
for(int a = 1; a <= k; ++a)
for(int b = 1; b <= n; ++b)
for(int c = 1; c <= n; ++c){
//走了a步,走到了b,第a - 1步在c
f[a][b] += f[a - 1][c] * bj[c][b];
}
cout << f[k][n];
强行加一维
n = read(), k = read();
for(int i = 1; i <= n; ++i)bj[i][j] = read();
f[0][1][1] = 1;
for(int a = 1; a <= k; ++a)
for(int d = 1; d <= n; ++d)
for(int b = 1; b <= n; ++b)
for(int c = 1; c <= n; ++c){
//走了a步,走到了b,第a - 1步在c
f[a][d][b] += f[a - 1][d][c] * bj[c][b];
}
cout << f[k][1][n];
提出 f a , d , b + = f a − 1 , d , c × b j c , b f_{a,d,b} += f_{a-1,d,c} \times bj_{c,b} fa,d,b+=fa−1,d,c×bjc,b
改为 f a d , b + = f ( a − 1 ) d , c × b j c , b f_{a_{d,b}} += f_{(a-1)_{d,c}} \times bj_{c,b} fad,b+=f(a−1)d,c×bjc,b
然后将 f a , f a − 1 , b j f_a, f_{a - 1}, bj fa,fa−1,bj当做 n n n 行 n n n 列矩阵
所以 f a = f a − 1 × b j f_a = f_{a - 1} \times bj fa=fa−1×bj
即得 f k = f 0 × b j k f_k = f_0 \times bj ^ k fk=f0×bjk
套矩阵快速幂
时间复杂度 O ( log n 3 log k ) O(\log {n ^ 3 \log k}) O(logn3logk)
例4
比例 4 4 4 多了路径长度罢了
把每一条边拆成由几个长度为一的边组成的链
然后由同一个点引出来的链可以合并
代码晚上补(
#include<bits/stdc++.h>
#define ll long long
#define itn int
#define ull unsigned long long
#define gt getchar
#define pt putchar
using namespace std;
inline ll read(){
ll x=0,f=1;char ch=gt();
while(!isdigit(ch)){
if(ch=='-')f=-1;ch=gt();}
while(isdigit(ch)){
x=(x<<1)+(x<<3)+(ch^48);ch=gt();}
return x*f;}
inline void print(ll x){
if(x<0)pt('-'),x=-x;if(x>9)print(x/10);pt(x%10+48);}
const ll M = 2009;
ll n, m;
struct Matrix{
ll a[105][105];//自行开空间
int n, m;
Matrix() {
memset(a, 0, sizeof a);}
}ans, f;
Matrix operator * (const Matrix &A, const Matrix &B){
//& 直接调用A, B.const 防止 A, B 被修改
Matrix Ans;
Ans.m = Ans.n = m;
for(int i = 1; i <= m; ++i)
for(int j = 1; j <= m; ++j)
for(int k = 1; k <= m; ++k)
Ans.a[i][j] = (Ans.a[i][j] + A.a[i][k] * B.a[k][j]) % M, Ans.a[i][j] %= M;
return Ans;
}
void qpow(ll b){
while(b){
if(b & 1)ans = (ans * f);
f = f * f;
b >>= 1;
}
}
int main(){
n = read();int t = read();
m = 9 * n;
ans.n = ans.m = f.n = f.m = 9 * n;
for(int i = 1; i <= n; ++i){
string s;
cin >> s;
for(int j = 1; j <= 8; ++j){
f.a[i + j * n][i + j * n - n] = 1;
ans.a[i + j * n][i + j * n - n] = 1;
}
for(int j = 1; j <= n; ++j){
int v = s[j - 1] - '0';
if(v) f.a[i][j + v * n - n] = 1, ans.a[i][j + v * n - n] = 1;
}
}
qpow(t - 1);
cout << ans.a[1][n] % M;
return 0;
}
质数判定
初等数论: 自然数范围
质数 & 素数 & prime : 只有自己和 1 1 1 为因子的数
合数 : 有大于 2 2 2 个因子的数
1既不是素数也不是合数
给定 x x x 判断 x x x 是否为质数
bool is_prime(int x){
if(x < 2)return 0;
for(int i = 2; i * i <= x; ++i)
if(x % i == 0)return 0;
return 1;
}
O ( x ) O(\sqrt x) O(x)只能处理 x ≤ 1 0 16 x \le 10 ^ {16} x≤1016
给定 x x x 分解质因数.(不能用筛)
void factorize(int x){
int cnt = 0;
for(int a = 2; a * a <= x; ++a){
if(x % a == 0){
++cnt;
prime[cnt] = a;
while(x % a == 0){
++num[cnt];
x /= a;
}
}
}
if(x != 1){
++cnt;
prime[cnt] = x;
num[cnt] = 1;
}
}
对于 a a a 第一次合条件时显然一定是质数,然后 x x x 除掉 a a a 直到不能再除,以后的 a a a 符合条件时也一定为质数
而对于特判,意思是如果x本身是质数就需要只输出 x x x 而循环不会记录 x x x 只能特判
例1
现有 1 , 2 , 3 , . . . , n 1, 2, 3, ..., n 1,2,3,...,n n 个数, 将它们分为几组,使得每组内之和都为质数,求最少分几组.
$ n \le 2000$
由哥德巴赫猜想知,大于等于4的偶数,可分为两个质数的和, 大于等于3的奇数,可分为3个质数的和
令 x = ∑ i = 1 n i = n × ( n + 1 ) 2 x = \sum_{i = 1}^n i = \frac{n \times (n + 1)}{2} x=∑i=1ni=2n×(n+1)
若