QBXT五一数学

· · 个人记录

zhx好闪,拜谢zhx 本人LaTeX存在问题,下课修改(

Day 1

上午

电脑1s跑3e8()

mod

a / b = c...d , a = b \cdot c + d b > d \ge 0 c = a / b , d = a \bmod b (a + b) \bmod c = (a \bmod c + b \mod c) \bmod c (a - b) \bmod c = (a \bmod c - b \bmod c) \bmod c (a \cdot b) \bmod c = (a \bmod c) \cdot (b \bmod c) \bmod c

例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

\therefore \gcd(a', b') = 1 \therefore \gcd(a - b, b) = \gcd((a' - b')g, b'g) = \gcd(a' - b', b') \cdot g = 1 \cdot g = g \gcd(a, b) = \gcd(a - b, b) = \gcd(a - 2b, b) = ..... = \gcd(a \bmod b, b)
代码
ll gcd(ll a, ll b){return a ? gcd(b % a, a) : b;}

时间复杂度O(log n)

证明:

a \ge b, \gcd(a, b) = \gcd(b, a \bmod $ $b) a \bmod b < a / 2
证明:

分类讨论

b <= a / 2时, a % b < b <= a / 2;

a >= b > a / 2时,a % b = a - b < a / 2;

所以每次 gcd 相当于将 a 除以 2

所以 O(\log n)

例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";

时间复杂度:

ans$ 每次都在变小, 而每次 $gcd$ 都可看做除 $2

所以最多除 \log{ans}

ans = a1$ , 所以 $O(n + \log a_1)

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 \le 10 ^ 9

例: y = 37

x ^ {37} = (x ^ {18}) ^ 2 \cdot x x ^ {18} = (x ^ 9) ^ 2 x ^ 9 = (x ^ 4) ^ 2 \cdot x x ^ 4 = (x ^ 2) ^ 2 x ^ 2 = x \cdot x

时间复杂度O(\log y)

代码
通用版(循环)
ll qpow(ll a, ll b, ll p){
    if(b == 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;
    if(y == 1)return a;
    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 \cdot m) \cdot B(m \cdot 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 \cdot m ^ 2)

$i,j,k$ 顺序枚举跑 $n = 1024$ ,耗时 $9.454s j,k,i$ 顺序枚举 $14.19s k,j,i$ : $12.46s i,k,j$ : $5.212s

所以循环顺序改成 i, k, j ( j 放在最后一位即可) 利用缓存机制

下午

例1

给定 n, p, 求斐波那契数列第 n \bmod p

数组 f 代表 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 = \begin{vmatrix}{} 1 & 1\\ 1 & 0 \end{vmatrix} \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} \times f_{i - 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 = \begin{vmatrix}{} 1 & 1 & 0\\ 0 & 0 & 1\\ 1 & 0 & 0 \end{vmatrix} \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\le i)

f_n \bmod p(1\le n\le 10^{18})

构造矩阵

\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 \times B \not = B \times A

例3

一个有向图, n 个点,走 k 步, 求从 1 走到 n 的方案数

n \le 100, k \le 100

考虑动态规划

bj_{i,j} 表示 ij 之间是否连边, f_{i,j} 表示走到 j 走了 i 步的方案数

所以答案即为 f_{k,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} \times bj_{c,b}

改为 f_{a_{d,b}} += f_{(a-1)_{d,c}} \times bj_{c,b}

然后将 f_a, f_{a - 1}, bj 当做 nn 列矩阵

所以 f_a = f_{a - 1} \times bj

即得 f_k = f_0 \times bj ^ k

套矩阵快速幂

时间复杂度 O(\log {n ^ 3 \log k})

例4

LuoguP4159

比例 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;
}

质数判定

初等数论: 自然数范围

质数 : 只有自己和 1 为因子的数

合数 : 有大于 2 个因子的数

给定 $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(\sqrt x)$ 只能处理 $x \le 10 ^ {16}

给定 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 第一次合条件时显然一定是质数,然后 x 除掉 a 直到不能再除,以后的 a 符合条件时也一定为质数

而对于特判,意思是如果x本身是质数就需要只输出 x 而循环不会记录 x 只能特判

例1

现有 1\backsim n, 将它们分为几组,使得每组内之和都为质数,求最少分几组.

n \le 2000

由哥德巴赫猜想知,大于等于 4 的偶数,可分为两个质数的和, 大于等于 3 的奇数,可分为 3 个质数的和

x = \sum_{i = 1}^n i = \frac{n \times (n + 1)}{2}

x 为偶数,输出 2 ,若 x 为奇数则分类讨论

x - 2 等于 0 则输出 2 ,否则输出 3

#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);}
bool check(int n){
    if(n <= 1)return 0;
    for(int i = 2; i * i <= n; ++i)if(n % i == 0)return 0;
    return 1;
}
int a[6005];
int main(){
    int n = read();
    int x = n * (n + 1) / 2;
    if(n == 1)return puts("-1"), 0;
    if(n == 2)return puts("1 1"), 0;
    if(n == 3)return puts("1 1 2"), 0;
    for(int i = 1; i <= n; ++i){
        a[i] = 1;
    }
    if(x & 1){
        if(check(x - 2)){
            a[2] = 2;
            for(int i = 1; i <= n; ++i)cout << a[i] << ' ';
        }
        else{
            a[3] = 3;
            for(int i = 1; i <= n; ++i)if(check(i) && check(x - 3 - i)){
                a[i] = 2;
                break;
            }
            for(int i = 1; i <= n; ++i)cout << a[i] << ' ';
        }
    }
    else{
        for(int i = 1; i <= n; ++i)if(check(i) && check(x - i)){
            a[i] = 2;
            break;
        }
        for(int i = 1; i <= n; ++i)cout << a[i] << ' ';
    }
    return 0;
}

Day 2

上午

逆元

(3 + 4) \bmod 5 = 2 (3 - 4) \bmod 5 = 4 [(1 + 7) \div 4] \bmod 5 = 2 (3 \div 4) \bmod 5 = ???

逆元:对于 a \div b \bmod p 找到一个整数 c ,使得 a \div b \bmod p = a \times c \bmod p

费马小定理:

p 为质数,且 1 \le a < p, a ^ {p - 1} \equiv 1 (\bmod p)

即得: a ^ {p - 2} \equiv \frac{1}{a} (\bmod p)

此时 a ^ {p - 2} 即为 a\bmod p 意义下的逆元

inline void inv(ll a, ll b, ll p){return 1ll * a * ksm(b, p - 2, p) % p}

这个方法只适合于 p 是质数 ,且 \gcd(a, p) = 1

欧拉定理:

$a ^ {\phi(p)} \equiv 1 (\bmod$ $p) 由欧拉定理可得: $a ^ {\phi(p) - 1} \equiv \frac{1}{a}(\bmod p)

p 为质数时, \phi(p) = p - 2 ,所以费马小定理是欧拉定理的特殊情况.

\phi (n):

n = p_1, \phi(n) = p_1 - 1

n = {p_1} ^ 2, \phi(n) = {p_1} ^ 2 - p_1 = p_1(p_1 - 1) n = {p_1} ^ k, \phi(n) = {p_1} ^ {k - 1} \cdot (p_1 - 1) = \frac{p_{i - 1}}{p_i} \cdot n n = p_1p_2, \phi(n) = n - \frac{n}{p_1} - \frac{n}{p_2} + \frac{n}{p_1p_2} = n(1 - \frac{1}{p_1})(1 - \frac{1}{p2}) n = {p_1}^{k_1} \cdot {p_2}^{k_2} \cdot {p_3}^{k_3} \cdot ...... \cdot {p_t}^{k_t} \phi(n) = n \cdot (1 - \frac{1}{p_1})(1 - \frac{1}{p_2})...(1 - \frac{1}{p_t})
ll getphi(ll n){
    ll phi = n;
    for(int i = 2; i* i <= n; ++i){
        if(n % i == 0){
            phi = phi / i * (i - 1);
            while(n % i == 0)
                n = n / i;
        }
    }
    if(n != 1)phi = phi / n * (n - 1);
    return phi;
}
O(\sqrt n)

例1

LuoguP3811

给定 n, p ,求 1 \backsim n 中每个数 \bmod p 意义下的逆元

##### 法一 先递推 $1 \backsim n$ 阶乘 $fac[i]

然后从 (n - 1) \backsim 1 推, \frac{1}{i!} = \frac{1}{(i + 1)!} \cdot (i + 1)

求出阶乘逆元 facinv[i]

然后逆元 \frac{1}{i} = (i - 1)! \cdot \frac{1}{i!}

cin >> n >> p;
fac[0] = 1;//阶乘
for(int i = 1; i <= n; ++i)
    fac[i] = 1ll * fac[i - 1] * i % p;
facinv[n] = ksm(fac[n], p -2, p);//阶乘逆元
for(int i = n - 1; i >= 1; --i)
    facinv[i]= 1ll * facinv[i + 1] * (i + 1) % p;
for(int i =1; i <= n; ++i)//逆元
    inv[i] = 1ll * fac[i - 1] * facinv[i] % p;
法二

假设 inv_1 \backsim inv_{i - 1} 都已经求出, inv_i = ?

p > i$ 设 $p = ki + r

所以 0 \equiv ki + r (\bmod p)

-r \equiv ki(\bmod$ $p) \frac{r}{i} \equiv -k (\bmod$ $p) ```cpp cin >> n >> p; inv[1] = 1; for(int i = 2; i <= n; ++i){ int k = p / i, r = p % i; //p = k * i + r //0 = k * i + r(mod p) //-r = k * i(mod p) //1 / i = -k / r(mod p) inv[i] = 1ll * (p - k) * inv[r] % p; } ``` ### 质数 若 $n$ 为质数, $n - 1 = d \times 2 ^ r

有一个 a ,1 \le a < n 使得

  1. a ^ d \bmod n = 1
  2. 存在 0 \le i < r, a^{d \times 2 ^ i} \bmod n = n - 1

随机代入 $a$ 检验, 满足的次数越多,则是质数的概率越大,有一个 $a$ 不满足条件,则一定不是质数 法一 ```cpp //n - 1 = d * 2 ^ r //找一个 1 <= a <= n //1. a ^ d % n = 1 //2. 存在0 <= i < r使得a ^ (d * 2 ^ i) % n = n - 1 //当n是质数时,至少一条成立 bool miller_rabin(int n, int a){ int d = n - 1, r = 0; while(d % 2 == 0)d /= 2, ++r; int x = ksm(a, d, r); if(x == 1)return 1; for(int i = 0; i < r; ++i){ if(x == n - 1)return 1; x = 1ll * x * x * % n; } return 0; }//O(logn) bool check(int n){ if(n < 2)return 0; for(int i = 1; i <= 23; ++i){ int a = rand()%(n - 1) + 1; if(!miller_rabin(n, a)) return 0; } return 1; } ``` 法二 ```cpp bool miller_rabin(int n, int a){ int d = n - 1, r = 0; while(d % 2 == 0)d /= 2, ++r; int x = ksm(a, d, r); if(x == 1)return 1; for(int i = 0; i < r; ++i){ if(x == n - 1)return 1; x = 1ll * x * x * % n; } return 0; }//O(logn) int p[] = {2, 3, 5, 7, 11, 13, 23, 37, 73}; bool check(int n){ if(n < 2)return 0; for(int i = 0; i < 8; ++i){ if(n == p[i])return 1; if(n % p[i] == 0) return 0; if(!miller_rabin(n, p[i] % n))reutrn 0; } return 1; } ``` ### exgcd 方程 $xa + yb = g \rightarrow (\gcd(a, b) = g) \gcd(b, a \bmod b) = g \rightarrow x'b + y'(a \bmod b) = g x'b + y'a - y'b \lfloor\frac{a}{b}\rfloor = g y'a + (x' - y' \lfloor\frac{a}{b}\rfloor) \cdot b = g
int exgcd(int a, int b, int &x, int &y){
    //求g = gcd(a, b)以及 xa + yb = g
    if(!b){x = 1, y = 0; return 0;}
    int xp, yp;
    int g = exgcd(b, a % b, xp, yp);
    //xp * b + yp * (a % b) = g
    //xp * b + yp * (a - b * (a / b)) = g
    //xp * b + yp * a - yp * b * (a / b) = g
    //yp * a + (xp - yp *(a/b)) * b = g
    x = yp;y = xp - yp * (a / b);
    return g;
}

裴蜀定理

对于 xa + yb (x, y\in Z) 能表示的最小正整数为 \gcd(a, b)

例1

LuoguP1082

ax \equiv 1(\bmod b)\rightarrow ax = yb + 1 \therefore ax - yb = 1

变为 ax + by = \gcd(a, b)

#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);}
ll n, p ,x, y;
void exgcd(ll a, ll b){
    if(!b)x = 1, y = 0;
    else exgcd(b, a % b), swap(x, y), y -= a / b * x;
}
int main(){
    n = read(), p = read();
    exgcd(n, p);
    cout << (x % p + p) % p << "\n";
    return 0;
}

下午

例2

中国剩余定理(CRT)/(ExCRT)

LuoguP1495

LuoguP4777

法一(大数翻倍):

首先,对于 x\equiv 1(\bmod 3), x\equiv 1( \bmod 5)

t = lcm(3,5)

(1 + t) \equiv 1( \bmod$ $3), (1 + t) \equiv 1( \bmod$ $5) t\equiv 0(\bmod$ $15)

发现 2 个同余方程可以合并:

x\equiv\begin{cases} a_1&(\bmod p_1)\\ a_2&(\bmod p_2) \end{cases}

如何合并成 x \equiv a( \bmod p):

x = a_1, 不断加上 p_1 , 次数加过 p_2 , 则无解

最后,p = lcm(p_1, p_2)a=x ,合并完毕,时间复杂度 O(p_2)

void solve(int p1, int a1, int p2, int a2, int &p, int &a){
    if(p1 < p2)swap(p1, p2),swap(a1, a2);//优化为O(min(p1, p2))
    int x = a1, g = gcd(p1, p2), l = p1 / g * p2;
    while(x <= l && x % p2 != a2)x += p1;
    if(x > l)p = a = -1;
    else p = l, a = x;
}

法二(ExGcd):

x \bmod p_1 = a_1, x \bmod p_2 = a_2, x \bmod p = a

x = k_1 \times p_1 + a_1 = k_2 \times p_2 + a_2

\therefore k_1 \times p_1 - k_2 \times p_2 = a_2 - a_1
void solve(int p1, int a1, int p2, int a2, int &p, int &a){
    int g, k1, k2;
    g = exgcd(p1, p2, k1, k2);
    k2 = -k2;
    if((a2 - a1) % g != 0)p = -1, a = -1;
    else{
    int k = (a2 - a1) / g;
        k1 = k1 * k, k2 = k2 * k;
        int x = k1 * p1 + a1;
        p = p1 / g * p2;
        a = (x % p + p) % p;
    }
}

大数翻倍完整代码:

#include<bits/stdc++.h>
#define ll __int128
#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);}

long long a[100005], p[100005];
int n;
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;
}
ll gcd(ll a, ll b){return a ? gcd(b % a, a) : b;}
void solve(ll p1, ll a1, ll p2, ll a2, ll &p, ll &a){
    if(p1 < p2)swap(p1, p2),swap(a1, a2);//优化为O(min(p1, p2))
    ll x = a1, g = gcd(p1, p2), l = p1 / g * p2;
    while(x <= l && x % p2 != a2)x += p1;
    if(x > l)p = a = -1;
    else p = l, a = x;
}
int main(){
    n = read();
    for(int i = 1; i <= n; ++i)p[i] = read(), a[i] = read(), a[i] %= p[i];
    for(int i = 2; i <= n; ++i){
        ll ta, tp;
        solve(p[i - 1], a[i - 1], p[i], a[i], tp, ta);
        if(tp != -1 && ta != -1)p[i] = tp, a[i] = ta;
    }
    cout << a[n];
    return 0;
}

筛法

给定 n 个数,从小到大输出 1 \backsim n 中的质数

暴力,枚举每个数 O(n\sqrt n)

miller$_$rabin$ ,枚举每个数 $O(n\log{n})

埃氏筛:

for(int a = 2; a <= n; ++a)
    for(int b = a + a; b <= n; b += a)
        nop[b] = 1;

for(int a = 2; a <= n; ++a)
    if(nop[a] == 0)p[++cnt] = a;

复杂度为 O(\frac{n}{1} + \frac{n}{2} + ... \frac{n}{n})

= n (\frac{1}{1} + \frac{1}{2} + ... \frac{1}{n})

调和级数, \approx O(n \log {n})

优化:

for(int a = 2; a <= n; ++a)
    if(nop[a] == 0)
        for(int b = a + a; b <= n; b += a)
            nop[b] = 1;

复杂度 O(n\log{\log{n}})

线性筛&欧拉筛

void Ola(){
    for(int i = 2; i <= n; ++i){
        if(nop[i] == 0)p[++cnt] = i;
        for(int j = 1; j <= cnt; ++j){//筛掉第j个质数的i倍
            int x = p[j] * i;
            if(x > n)break;
            nop[x] = 1;
            if(i % p[j] == 0)break;//保证每个数只被最小质因子筛掉
        }
    }
}

时间复杂度 O(n)

积性函数

积性函数: 当 \gcd(a, b) = 1 时, f(a)f(b) = f(ab)

例子: \phi(x)

\phi(n) = n(1 - \frac{1}{p_1})(1 - \frac{1}{p_2})...(1 - \frac{1}{p_t}) n = {p_1}^{k_1}{p_2}^{k_2}...{p_t}^{k_t} \phi(m) = m(1 - \frac{1}{p_1})(1 - \frac{1}{p_2})...(1 - \frac{1}{p_w}) m = {p_1}^{r_1}{p_2}^{r_2}...{p_w}^{r_w} nm = {p_1}^{k_1}{p_2}^{k_2}...{p_t}^{k_t} \cdot {p_1}^{r_1}{p_2}^{r_2}...{p_w}^{r_w} \phi(nm) = n(1 - \frac{1}{p_1})(1 - \frac{1}{p_2})...(1 - \frac{1}{p_t}) \cdot m(1 - \frac{1}{p_1})(1 - \frac{1}{p_2})...(1 - \frac{1}{p_w}) \therefore \phi(n)\phi(m) = \phi(nm)

线性预处理 \phi(x)

void Ola(){
    for(int i = 2; i <= n; ++i){
        if(nop[i] == 0)p[++cnt] = i, phi[i] = i - 1;
        for(int j = 1; j <= cnt; ++j){
            int x = p[j] * i;
            if(x > n)break;
            nop[x] = 1;
            phi[x] = phi[p[j]] * phi[i];
            if(i % p[j] == 0){
                phi[x] = p[j] * phi[i];
                break;
            }
        }
    }
}

Day 3

上午

baby step giant step算法(北上广深算法)

给定 a, b, p 求解 a ^ x \bmod p = b

a, b, p \le 10^9

暴力容易写出 O(x)

//此文章第1145行(
int solve (int a, int b, int p){
    ll v = 1;
    for(int x = 0; ; ++x){
    if(v == b)return x;
        v = 1ll * v * a % p;
        f(v == 1)return -1;
    }
}

由费马小定理知:

a ^ {p - 1} \equiv 1(\bmod p)

所以

a^{p - 1 + k} = a^{p - 1} \times a ^ k \equiv a ^ k (\bmod p)

可得循环节只有 p - 1 的长度

修改为 O(p - 1)

int solve (int a, int b, int p){
    ll v = 1;
    for(int x = 0; x < p - 1; ++x){
    if(v == b)return x;
        v = 1ll * v * a % p;
        f(v == 1)return -1;
    }
}
先将 $a^0 \backsim a^{p - 1}$ 分为几组, 每组为 $a^0, a^1,... a^{s - 1} a^s, a^{s - 1}, ..., a ^ {2s - 1} a^{2s}...... ...

查看第一组是否有解,若有,则跳出循环,否则查看第二组......

和暴力复杂度其实一样

我们可以发现如果 b 在第二组中出现,则一定在第一组中有 b \cdot a^{-s} 出现,

如果 b 在第二组中出现,则一定在第一组中有 b \cdot a^{-2s} 出现

...

所以可以检查第一组得到解, 有分块的思想

代码

int solve (int a, int b, int p){
    int s = sqrt(p);
    int v = 1;
    set<int> se;//集合STL
    for(int i = 0; i < s; ++i){
        se.insert(v);
        v = 1ll * v * a % p;
    }
    //O(p/s)
    for(int i = 0; i * s <= p; ++i){//检查答案是否在第i行 
    //看b * a ^ (-i* s) 是否在第0行出现
        int c = 1ll * b * ksm(ksm(a, i * s, p), p - 2, p) % p;
        if(se.count(c) != 0){
            int v = ksm(a, i * s, p);//第i行第一个数
            for(int j = i * s; ; ++j){//最坏O(s)
                if(v == b)return j;
                v = 1ll * v * a % p;
            } 
        }
    } 
    //总 : O(max(s, p/s)); 即得s = sqrt(p)最合适 
    //所以时间复杂度 O(sqrt(p)) 
    return -1;
}
//1234行(

时间复杂度 O(\sqrt p) (分块复杂度

例1

LuoguP3846 模板,见代码:

#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define gt getchar
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;}
ll ksm(ll a, ll b, ll p){
    if(b == 0)return 1; 
    ll ans = 1;
    while(b){
        if(b&1)ans = ans * a % p;
        b >>= 1;
        a = a * a % p; 
    }
    return ans;
}
ll solve(ll a, ll b, ll p){
    int s = sqrt(p);
    int v = 1;
    set<int> se;
    for(int i = 0; i < s; ++i){
        se.insert(v);
        v = 1ll * v * a % p;
    }
    for(int i = 0; i * s <= p; ++i){
        int c = 1ll * b * ksm(ksm(a, i * s, p), p - 2, p) % p;
        if(se.count(c) != 0){
            int v = ksm(a, i * s, p);
            for(int j = i * s; ; ++j){
                if(v == b)return j;
                v = 1ll * v * a % p;
            }
        }
    }
    return -1;
}
int main(){
    int p, b, n;
    cin >> p >> b >> n;
    ll ans = solve(b, n, p);
    if(ans == -1)cout << "no solution";
    else cout << ans;
    return 0;
}

组合数学

组合睡学(

核心: 计数, 方案数

加法原理 : 从 AB 上面 3 条路,下面 2 条路,总共 5 种方案 (同一阶段内加)

乘法原理 ; 从 AB3 条路, BC2 条路, AC6 种方案 (不同阶段内乘)

排列: n 个人选 m 个人, 考虑 m 个人的顺序

1 个人有 n 种选法

2 个人有 n - 1 种选法

m 个人有 n - m + 1 种选法,

P \binom{n}{m} = n \cdot (n - 1)(n - 2)...(n - m + 1) = \dfrac{n!}{(n - m)!}

组合: n 个人选 m 个人, 不考虑 m 个人的顺序

C \binom {n}{m} = \dfrac{n \cdot (n - 1)...(n - m + 1)}{m!} = \dfrac{n!}{(n - m)! m!} = \dfrac{P \binom{n}{m}}{m!} C \binom{n}{m} = \dfrac{n!}{(n - m)!m!}

组合数一些式子:

  1. 一个都不选或者全选,只有一种做法:

    \binom{n}{0} = 1 = \binom{n}{n}
  2. m 个或者选 n - m 个丢掉实质一样:

    \binom{n}{m} = \binom {n}{n - m}
  3. m 个, 考虑分情况讨论:: 1) 选第 1 个物品, 还需要从剩下的 n - 1 中选 m - 1

    2) 不选第 1 个物品, 还需要从剩下的 n - 1 中选 m

    \binom{n}{m} = \binom{n - 1}{m - 1} + \binom {n - 1}{m}

    预处理求所有的组合数

for(int i = 0; i <= n; ++i){
    c[i][0] = 1;
    for(int j = 1; j <= i; ++j)
        c[i][j] = c[i - 1][j - 1] + c[i - 1][j];
}

此时的 C 数组是个杨辉三角形

1 1,1 1,2,1 1,3,3,1 1,4,6,4,1
  1. 选任意多个物品:

    \binom{n}{0} + \binom{n}{1} + \binom{n}{2} + ... +\binom{n}{n} = 2 ^ n
  2. n \ge 1 \binom{n}{0}-\binom{n}{1}+\binom{n}{2}-\binom{n}{3}+... \pm \binom{n}{n} = 0

    由第 3 个式子可得,所有奇数位置的和等于杨辉三角中上一行的和,所有偶数位置的和也等于杨辉三角上一行的和都等于 2^{n - 1}

    所以奇数位置之和与偶数位置之和之差为 0

(x + y)^0 = 1 (x + y)^1 = x + y (x + y)^2 = x ^ 2 + 2xy + y ^ 2 (x + y)^3 = x ^ 3 + 3x^2y + 3xy^2 +y^3 (x + y)^4 = x ^ 4 + 4x^3y + 6x^2y^2 + 4xy^3 + x^4

观察系数,发现是杨辉三角

\therefore (x + y)^n = \binom{n}{0}x^ny^0 + \binom{n}{1}x^{n - 1}y^1 + ...+\binom{n}{n}x^0y^n

即为

(x + y) ^n = \sum_{i = 0}^{n}\binom{n}{i}x^{n - i}y^i
\binom{n}{m} =\binom{n-1}{m - 1}+\binom{n - 1}{m} =\binom{n - 2}{m - 2}+2\binom{n - 2}{m - 1}+\binom{n - 2}{m} =\binom{n - 3}{m - 3}+3\binom{n - 3}{m - 1}+3\binom{n - 3}{m - 2}+\binom{n - 3}{m} ...... = \sum_{i = 0}^{k}\binom{k}{i} \cdot \binom{n - k}{m - i}

例1

考虑原版,每个数只能选一次其实就是求不等式 $$ 1 \le a_1 < a_2 < ... < a_m \le n $$ 的解的个数 这道题可以看做 $$ 1 \le b_1 \le b_2 \le ... \le b_m \le n $$ 的解的个数 设 $c_1 = b_1, c_2=b_2+1...,c_m = b_m + m - 1

就可以看做

1 \le c_1 < c_2 < ... < c_m \le n + m + 1

的解的个数 = \binom{n + m-1}{m}

ans = \binom{n + m -1}{m}

例2

\binom{n}{m} \bmod p

1.n,m \le 10^{18}, p = 1

ans = 1

2.n, m \le 1000, p 随意

用杨辉三角递推O(nm)

for(int i = 0; i <= n; ++i){
    c[i][0] = 1;
    for(int j = 1; j <= i; ++j)
        c[i][j] = (c[i - 1][j - 1] + c[i - 1][j]) % P;
}
cout << c[n][m];

LuoguP2822

代码下课补(

#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define gt getchar
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;}
ll t, k;
ll c[2005][2005], qzh[2005][2005];
int main(){
    t = read(), k = read();
    memset(c, -1, sizeof c);
    for(int i = 0; i <= 2000; ++i){
        c[i][0] = 1;
        for(int j = 1; j <= i; ++j){
            c[i][j] = (max(c[i - 1][j - 1], (ll)0) + max(c[i - 1][j],(ll)0)) % k;
        }
    }
    for(int i = 1; i <= 2000; ++i){
        for(int j = 1; j <= 2000; ++j){
            qzh[i][j] = qzh[i - 1][j] + qzh[i][j - 1] - qzh[i - 1][j - 1];
            if(c[i][j] == 0)qzh[i][j]++;
        }
    }
    for(int i = 1; i <= t; ++i){
        int n = read(), m = read();
        cout << qzh[n][m] << "\n";
    }
    return 0;
}

3.n,m \le 10^6, p 为质数

逆元 O(n\log p)

\binom{n}{m} = \dfrac{n!}{(n - m)!m!} = n! [(n - m)!]^{p - 2}(m!)^{p - 2} \bmod p
fac[0] = 1;
for(int i = 1; i <= 1000000; ++i)fac[i] = 1ll * fac[i - 1] * i % p;
cout << fac[n] * ksm(fac[m], p - 2, p) % p * ksm(fac[n - m], p - 2, p) % p;

下午

4.n \le 10^9, m \le 10^3

\binom{n}{m} = \dfrac{n!}{(n - m)!m!} = \dfrac{n(n - 1)(n - 2)..(n - m + 1)}{1 \times 2 \times 3 \times...\times m}

然后把分母约为 1, 分子乘起来即可 O(m^2\log m)

for(int i = 1; i <= m; ++i){
    fenzi[i] - i;
    fenmu[i] - n - i + 1; 
}
for(int i = 1; i <= m; ++i){
    for(int j = 1; j <= m; ++j){
        int g = gcd(fenzi[i], fenmu[j]);
        fenzi[i] /= g;
        fenmu[j] /= g;
    }
} 
int ans =1;
for(int i = 1; i <= m; ++i)ans = 1ll * fenzi[i] % p;

5.n, m \le 10 ^ 9, p \le 100 为指数

先将 $n, m$ 转换为 $p$ 进制. 短除法转换进制 如 $n = 25, m = 12, p = 3$ , $n_{(3)} = 221, m_{(3)} = 110 \binom{25}{12} \bmod p = \binom{n_1}{m_1}\binom{n_2}{m_2}\binom{n_3}{m_3} \bmod p
int lucas(int n, int m, int p){
    while(n){
        ++x[0];
        x[x[0]] = n % p;
        n = n / p;
    }//n的p进制表示 
    while(m){
        ++y[0];
        y[y[0]] = m % p;
        m = m / p;
    }
    int ans = 1;
    for(int i = 1; i <= x[0]; ++i)
        ans = 1ll * ans * c[x[i]][y[i]] % p;
    return ans; 
}
int main(){
    cin >> n >> m >> p;
    for(int i = 0; i <= p; ++i){
        c[i][0] = 1;
        for(int j = 1; j <= i; ++j){
            c[i][j] = (c[i - 1][j - 1] + c[i - 1][j]) % p;
        }
    }
    return 0;
}
O(\log n)

LuoguP3807

卢卡斯定理只适用于 p 为质数, 当 p 不为质数时, 将其质因数分解

然后中国剩余定理合并, 计算可得

例3

x 拆为 k 个不同组合数之和

x \le 10 ^ 9, k \le 10^3

酱汁构造(

x = \overbrace{1 + 1 + 1 + ...+1}^{k - 1} +(n - k + 1)

例4

比较 \binom{n1}{m1}\binom{n2}{m2} 大小

\log \binom{n}{m} = \log\dfrac{n!}{(n - m)! m!} = \log {n!} - \log {(n - m)!} - \log{m!} \log {n!} = \log1 + \log2 + ... + \log n
fac[0] = 0;
for(int i  =1; i <= 1000000; ++i){
    fac[i] = fac[i - 1] + log(i); 
}
double logcnm(int n, int m){
    return fac[n] - fac[m] - fac[n - m];
}

例5

找到 k 个不同的组合数,使得这 k 个组合数之和最大

对于每个组合数 \binom{a}{b}, a, b \le n

观察杨辉三角

1 1,1 1,2,1 1,3,3,1 1,4,6,4,1

最底层中间的数最大, 往外扩散逐渐减小

单调队列, 结构体重载运算符

代码晚上补(

例6

LuoguP3746

f(n, r) = \sum_{i = 0}^{\inf}C(nk, ik + r)

展开 k

= \sum_{i = 0}^{\inf}\sum_{j = 0}^{k}C(k, j) \cdot C(nk - k, ik + r - j) = \sum_{j = 0}^{k}\sum_{i = 0}^{\inf}C(k, j) \cdot C(nk - k, ik + r - j) = \sum_{j = 0}^{k}\sum_{i = 0}^{\inf}C(k, j) \cdot C(nk - k, ik + r - j) = \sum_{j = 0}^{k}C(k, j)\sum_{i = 0}^{\inf} \cdot C((n - 1) k, ik + r - j) = \sum_{j = 0}^{k}C(k, j) \cdot f(n - 1, r - j)

D(r - j, r) = C(k,j) f_n(1, r) = \sum_{j = 0}^{k}f_{n - 1}(1, r - j) \cdot D(r - j, r)

矩阵快速幂

代码晚上能补出来?(

#include<bitsdc++.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);}
ll n, p, k, r;
struct Matrix{
    ll a[55][55];//自行开空间 
    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;
        Ans.n = A.n, Ans.m = B.m;
        for(int i = 0; i < A.n; ++i)
            for(int j = 0; j < B.m; ++j)
                for(int k = 0; k < A.m; ++k)
                    Ans.a[i][j] = (Ans.a[i][j] + A.a[i][k] % p * B.a[k][j] % p) % p, Ans.a[i][j] %= p;
        return Ans;
    }
void ksm(ll b){
    while(b){
        if(b & 1)ans = ans * base;
        base = base * base;
        b >>= 1;
    }
}
void init(){    
    ans.a[0][0] = 1;
    ans.n = base.n = base.m = k, ans.m = k;
    for(int i = 0; i < k; ++i){
        base.a[i][i] = 1;
        base.a[i][(i + 1) % k]++;
    }
}
int main(){
    n = read(), p = read(), k = read(), r = read();
    init();
    ksm(n * k);
    cout << ans.a[0][r];
    return 0;
}

抽屉原理

$kn + 1$ 个东西,放入 $n$ 个抽屉里, 至少有一个抽屉有至少 $k + 1$个东西 #### 例1 给定 $n$ 个数, 要求从中选出任意多个数, 使得它们的和为 $c$ 的倍数 $c \le n \le 10^5

用前缀和做:

qzh$ 数组中存放 $1 +n$ 个数: $qzh_0, qzh_1, ... ,qzh_n

将每个元素 \bmod c 后的结果分类 0, 1, 2, ..., c - 1

据抽屉原理知一定有同类的结果, 即得答案

代码等补(

例2

平面上 n 个点 (x_i, y_i)

用三个大小为 L \times L 的正方形覆盖住所有点

求最小的 L

显然二分答案, 如何写 check 函数

得到这些点 x, y 的最大最小值,用最小的矩形框住所有点

先用一个正方形盖住一角, 将剩下的点继续用最小矩形框住,再用一个正方形盖住一角,最后检查剩下的点能否盖住即可.

check中循环 4 \times 4.

O(4 \times 4 \times n)

代码晚上补啊啊啊啊啊啊啊啊啊啊

容斥

A_1 为学语文的人的集合, A_2 为学数学的人的集合, A_3 为学英语的人

同时学语文和数学的人: A_1 \bigcap A_2

学语文或数学的人: A_1 \bigcup A_2

|A_1 \bigcup A_2| = |A_1| + |A_2| - |A_1 \bigcap A_2| |A_1 \bigcup A_2 \bigcup A_3| = |A_1| + |A_2| + |A_3| - |A_1 \bigcap A_2| - |A_2 \bigcap A_3| - |A_1 \bigcap A_3| + |A_1 \bigcap A_2 \bigcap A_3| |A_1 \bigcup A_2 \bigcup A_3 \bigcup A_4| =|A_1| + |A_2| + |A_3| + |A_4| -|A_1 \bigcap A_2|-|A_1 \bigcap A_3|-|A_1 \bigcap A_4|-|A_2 \bigcap A_3|-|A_2 \bigcap A_4|-|A_3 \bigcap A_4| + |A_1 \bigcap A_2 \bigcap A_3|+ |A_1 \bigcap A_2 \bigcap A_4|+ |A_1 \bigcap A_3 \bigcap A_4|+ |A_2 \bigcap A_3 \bigcap A_4| -|A_1 \bigcap A_2 \bigcap A_3 \bigcap A_4|

例1

1. 夫妻不能相邻 2. 旋转算同一种方案 先不考虑要求1,并且坐成一排,答案 $(2n)!

Day 4

上午

坐成一圈, 答案 \frac{(2n)!}{2n - 1} = {(2n - 1)}!

这样,所有不合法方案一定被减掉,但是, 有可能减多次

$+\binom{n} {2} \cdot (2n - 3)! \cdot 2 ^ 2$:加上两对夫妻强制相邻的方案数 $-\binom{n} {3} \cdot (2n - 4)! \cdot 2 ^ 3$:加上三对夫妻强制相邻的方案数 剩下同上 总式: $$ ans = \sum_{i = 0}^{n}\binom{n}{i} \cdot (2n - i - 1)! \cdot 2 ^ i \cdot (-1)^i $$ 容斥通用做法: 要满足 $n$ 个条件, 先减满足前 $1$ 个条件的方案数, 再加满足前 $2$ 个条件的方案数....... #### 例2 询问 $1 \backsim n$ 中有多少数能表示成 $x^ y, y > 1$ 的形式 $n \le 10 ^{18} $1 \backsim n$ 中能表示成 $x ^ 3$ 的 $x$ 有 $\sqrt[3]{n}$ 个 $1 \backsim n$ 中能表示成 $x ^ y$ 的 $x$ 有 $\sqrt[y]{n}$ 个 $y ^ 6 = (y ^ 2) ^ 3 = (y ^ 3) ^ 2

所以要减去 \sqrt[6]{n} 保证不重

#include<cmath>

cin >> n;
for(int a = 2; a <= 64; ++a){
    num[a] = 0;//代表x^a 这种形式的数算了几次 
}
for(int a = 2; a <= 64; ++a){
    //pow(x, y) = x ^ y;
    //pow(x, 0.5) = sqrt(x);
    ll v = floor(powl(n, 1.0 / a)) - 1;
    int d = 1 - num[a];
    ans += v * d;
    num[a] += d;
    for(int b = a; b <= 64; b += a){
        num[b] += d;
    } 
} 
cout << ans + 1;//补上1 

LuoguP9118

矩阵

解方程:

\begin{cases} x_1 + x_2 = 2\\ x_1 + x_2 = 4 \end{cases}

消元即可

如何求 n 元一次方程

\begin{cases} a_{1,1}x_1+a_{1,2}x_2+...+a_{1,n}x_n = b_1(1)\\ a_{2,1}x_1+a_{2,2}x_2+...+a_{2,n}x_n = b_2(2)\\ ......\\ a_{n,1}x_1+a_{n,2}x_2+...+a_{n,n}x_n = b_n(n)\\ \end{cases}

高斯消元

(2) - (1) \cdot \frac{a_{2,1}}{a_{1,1}} (3) - (1) \cdot \frac{a_{3,1}}{a_{1,1}}

......

消掉 x_1

剩下 n - 1 个方程和未知数.....

再不断消掉其他的位置数,只剩一个未知数和方程,然后回带

\begin{cases} x_1 + x_2 + x_3= 3\\ 2x_1 + 3x_2 + 4x_3= 9\\ 3x_1 + 7x_2 + 10x_3= 20\\ \end{cases}

变成

\begin{cases} x_1 + x_2 + x_3= 3\\ 0 + x_2 + 2x_3= 6\\ 0 + 4x_2 + 7x_3= 11\\ \end{cases}

变成

\begin{cases} x_1 + x_2 + x_3= 3\\ 0 + x_2 + 2x_3= 6\\ 0 + 0-1x_3= -1\\ \end{cases}

代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
double a[105][105];
int n;
double x[105];
void Gauss(){
    for(int i = 1; i <= n; ++i){
        //把xi从第i+1到第n个方程中消掉
        for(int j = i; j <= n; ++j){
            if(fabs(a[j][i]) > fabs(a[i][i])){//主元消元法
                swap(a[i],a[j]);
                break;
            }
        }
        for(int j = i + 1; j <= n; ++j){//把xi从第j个方程中消掉 
            double ratio = a[j][i] / a[i][i];
            for(int k = 1; k <= n + 1; ++k){
                a[j][k] -= a[i][k] * ratio;
            } 
        } 
    }
    for(int i = n; i >= 1; --i){
        for(int j = i + 1; j <= n; ++j)
            a[i][n + 1] -= a[i][j] * x[j]; 
        x[i] = a[i][n + 1] / a[i][i];
    }
}
int main(){
    cin >> n;
    for(int i = 1; i <= n; ++i){
        for(int j = 1; j <= n; ++j)
            cin >> a[i][j];
        cin >> a[i][n + 1];
    }
    Gauss();
    //a[1][1] * x[1] + a[1][2] * x[2] + ... + a[1][n] * x[n] = a[1][n + 1];
    //a[2][1] * x[1] + a[2][2] * x[2] + ... + a[2][n] * x[n] = a[2][n + 1];
    // ............................................................
    //a[n][1] * x[1] + a[n][2] * x[2] + ... + a[n][n] * x[n] = b[n][n + 1];
    for(int i = 1; i <= n; ++i){
        printf("%.2lf\n", x[i]);
    }
    return 0;
}
O(n^3)

可以用最小公倍数做, 避免精度问题

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int a[105][105];
int n;
double x[105];
void Gauss(){
    for(int i = 1; i <= n; ++i){
        //把xi从第i+1到第n个方程中消掉
        for(int j = i; j <= n; ++j){
            if(fabs(a[i][j] != 0)){//实数判断是否为0 
                swap(a[i],a[j]);
                break;
            }
        }
        for(int j = i + 1; j <= n; ++j){//把xi从第j个方程中消掉 
            if(a[j][i] == 0)continue;
            int l = a[i][i] / gcd(abs(a[i][i]),abs(a[j][i])) * a[j][i];
            int ratioi = l / a[i][i];
            int ratioj = l / a[j][i];
            for(int k = 1; k <= n + 1; ++k)
                a[j][k] = a[j][k] * ratioj - a[i][k] * ratioi;
        } 
    }
    for(int i = n; i >= 1; --i){
        for(int j = i + 1; j <= n; ++j)
            a[i][n + 1] -= a[i][j] * x[j]; 
        x[i] = (double)a[i][n + 1] / a[i][i];
    }
}
int main(){
    cin >> n;
    for(int i = 1; i <= n; ++i){
        for(int j = 1; j <= n; ++j)
            cin >> a[i][j];
        cin >> a[i][n + 1];
    }
    Gauss();
    //a[1][1] * x[1] + a[1][2] * x[2] + ... + a[1][n] * x[n] = a[1][n + 1];
    //a[2][1] * x[1] + a[2][2] * x[2] + ... + a[2][n] * x[n] = a[2][n + 1];
    // ............................................................
    //a[n][1] * x[1] + a[n][2] * x[2] + ... + a[n][n] * x[n] = b[n][n + 1];
    for(int i = 1; i <= n; ++i){
        printf("%.2lf\n", x[i]);
    }
    return 0;
}

单位矩阵 I :

I = \begin{vmatrix}{} 1 & 0 & 0 &...& 0\\ 0 & 1 & 0 &...& 0\\ 0 & 0 & 1 &...& 0\\ ...&...&...&...&...\\ 0 & 0 & 0 &...& 1\\ \end{vmatrix} AI = IA

A \cdot B = I

BA 的倒数

A \cdot B = CC_{i,j} = \sum_{k = 1}^{n}A_{i,k} \cdot B_{k, j}

就有 n ^ 2 个未知数和方程, 高斯消元求解 B, 时间复杂度 O(n^6)

A = \begin{vmatrix}{} 1 & 2 \\ 3 & 4 \end{vmatrix} A\times \begin{vmatrix}{} 0 & 1 \\ 1 & 0 \end{vmatrix} = \begin{vmatrix}{} 2 & 1 \\ 4 & 3 \end{vmatrix} \begin{vmatrix}{} 0 & 1 \\ 1 & 0 \end{vmatrix} \times A =\begin{vmatrix}{} 3 & 4 \\ 1 & 2 \end{vmatrix} \begin{vmatrix}{} 1&0&0&0\\ 0&0&1&0\\ 0&1&0&0\\ 0&0&0&1 \end{vmatrix} \times \begin{vmatrix}{} 1&2&3&4\\ 2&3&4&1\\ 3&4&1&2\\ 4&1&2&3 \end{vmatrix} =\begin{vmatrix}{} 1&2&3&4\\ 3&4&1&2\\ 2&3&4&1\\ 4&1&2&3 \end{vmatrix}

所以矩阵可以高斯消元消成单位矩阵,在消元过程中相当于矩阵乘 B

下午

概率和期望

事件

扔一个骰子

样本空间: 1,2,3,4,5,6

$A = \{1,2,3\}

样本点组合成事件

P(A)= \frac{1}{2}

A = \{1,2,3\}, B= \{2,3,4\} A \cup B = \{1,2,3,4\} A \cap B= \{2,3\}

减法: 把 A 中出现在 B 中的元素删掉

A- B = \{1\} = A - A \cap B

概率

概率定义:为样本空间的每一个事件定义一个实数,这个实数称为概率

事件的概率等于它所包含的样本点的概率之和

0 \le P(A) \le 1 n$ 个样本点: $B_1, B_2, ..., B_n P(B_1)+ P(B_2) + .. + P(B_n) = 1 $P(A) = 1$ : 必然事件 $P(A|B)$ : 条件概率: $B$ 发生的情况下 $A$ 发生的概率 例子: $1,2,3,4,5,6 A= \{1\}, B= \{1,2,3\} P(A|B) = \frac{1}{3} C = \{1,2,3\}, D = \{1,3,5\} P(C|D) = \frac{2}{3}

公式1: P(A|B) = \frac{P(AB)}{P(B)}

\therefore P(C|D) = \frac{P(CD)}{P(D)} = \frac{\frac{1}{3}}{\frac{1}{2}} = \frac{2}{3}

公式2: P(A|B)P(B) = P(AB)

独立事件 : 两个事件 A, B,A 的发生不影响 B 的发生, B 也不影响 A

即: P(A)P(B) = P(AB), P(A) = P(A|B)

扔两次骰子,第一次与第二次互相不影响,互为独立事件

期望

扔骰子: 1,2,3,4,5,6

数的期望: 1,2,3,4,5,6

概率:\frac{1}{6},\frac{1}{6},\frac{1}{6},\frac{1}{6},\frac{1}{6},\frac{1}{6}

期望值: 1 \times \frac{1}{6} + 2\times \frac{1}{6} + 3\times \frac{1}{6} + 4\times \frac{1}{6} + 5\times \frac{1}{6} + 6\times \frac{1}{6} = \frac{7}{2}

数的平方的期望 1,4,9,16,25,36

此时的期望值 \frac{91}{6}

E[f(X)] = \sum f(X) P(X =x)

期望的和 等于和 的期望

E[x_1+x_2] = E[x_1] + E[x_2] E[x_1+{x_1}^2] = E[x_1] + E[{x_1}^2]
例1

三张一样的卡, 其中一张两面黑,一张两面红,一张一面红一面黑

随机抽取一张放在桌子上,红色面朝上,另一面为黑色的概率是多少是多少

因为有三张牌,而又分正反面,所以朝上的情况有 6

1R,2R,3R,4B,5B,6B P(\text{下黑|上红}) = \dfrac{P(\text{下黑上红})}{P(\text{上红})} = \frac{1}{3}
例2
公平. 第一个人的概率是: $\frac{1}{n}

第二个人的概率是: \frac{n - 1}{n} \cdot \frac{1}{n - 1} = \frac{1}{n}

第三个人的概率是: \frac{n - 1}{n}\frac{n - 2}{n - 1} \cdot \frac{1}{n - 2} = \frac{1}{n}

以此类推

例3

设男女人口比例为 51:49, 男性色盲率为 2\%, 女性色盲率为 0.25\%

P(\text{男|盲}) = \dfrac{P(\text{男,盲})}{P(\text{盲})} = \dfrac{51\% \cdot 2\%}{51\% \cdot 2\%+49\% \cdot 0.25\%}
例4

一个人左右口袋各一盒火柴,每盒 n 支,每次抽烟时随机选一盒点火,由于习惯, 选右口袋的概率大于二分之一 (P > \frac{1}{2}) ,, 问下面两种情形概率是否相等,试求概率值

  1. 某次发现取的这一盒已经空了, 这是另一盒恰好有 m 支火柴

  2. 某次用完一盒时另一盒恰好有 m 盒火柴 1) 概率
    1-p n n + 1
    p n n - m

当右边先取完,p^{(n+1)}(1-p)^{(n-m)}

当左边先取完,(1-p)^{(n+1)}p^{(n-m)}\cdot C(2n-m,n)

例5

在小葱和小泽面前有三瓶药,其中有两瓶是毒药,每个人必须喝 一瓶。小葱和小泽各自选了一瓶药,小泽手速比较快将药喝了下去,然 后就挂掉了.。小葱想活下去,他是应该喝掉手上的这瓶,还是另外剩下的一瓶 呢?

其实是三门问题.

有三扇门, 两个门后面是羊,一个门后面是车,你选定了一个门,主持人打开了一个门显示为羊

1 2 3

假设你选第一个门 (p = \frac{1}{3}), 主持人不论打开剩下的那扇门都应该不换,选第二个门 (p = \frac{1}{3}), 主持人打开第 3 个门, 应该换, 选第三个门 (p = \frac{1}{3}), 主持人打开第 2 个门,应该换.

所以 P(\text{换}) = \frac{2}{3}

1 2 3

小泽喝药死了是随机行为,不一定是因为毒药

所以换不换都一样

例6

过河有两个路

问选哪个.

首先,选 1 活的概率是 (\frac{99}{100}) ^ {100}2 活的概率是 (\frac{999}{1000}) ^ {1000}

\because \frac{1}{1000} < \frac{1}{999} \therefore 1 - \dfrac{1}{1000} > 1-\dfrac{1}{999}

\dfrac{999}{1000} > \dfrac{998}{999}

\therefore \dfrac{999}{1000}>\dfrac{998}{999}>...>\dfrac{990}{991}

\because \dfrac{99}{100} =\dfrac{990}{1000}= \dfrac{999}{1000} \cdot \dfrac{998}{999} \cdot ... \cdot \dfrac{990}{991}

\therefore \dfrac{99}{100} < (\dfrac{999}{1000}) ^ {100}, \dfrac{99}{100} < \dfrac{999}{1000} \therefore (\dfrac{99}{100}) ^ {100} < (\dfrac{999}{1000}) ^ {100} \therefore (\dfrac{99}{100}) ^ {100} < (\dfrac{999}{1000}) ^ {1000}
例7

小胡站在原点,手里拿着两枚硬币。抛第一枚硬币正面向上的概 率为 q,第二枚正面向上的概率为 r

小胡开始抛第一枚硬币,每次抛到反面小胡就向 y 轴正方向走一 步,直到抛到正面。

接下来小胡继续抛第一枚硬币,每次抛到反面小胡就向 x 轴正方 向走一步,直到抛到正面。

现在小胡想回来了,于是他开始抛第二枚硬币,如果小胡抛到正 面小胡就向 y 轴的负方向走一步,否则小胡就向 x 轴的负方向走一 步。

现在小胡想知道他在往回走的时候经过原点的概率是多少呢?

\sum_{x = 0}^{\inf}\sum_{y=0}^{\inf}\overbrace{\dfrac{(1-p)^x \cdot p}{1}}^{\text{走到(x,0)}} \overbrace{\cdot\dfrac{(1 - p)^y \cdot p}{2}}^{\text{走到(x,y)}} \overbrace{\cdot q^ x\cdot(1-q)^y \cdot \binom{x+y}{x}}^{\text{回到(0,0)}} =\sum_{x = 0}^{\inf}\sum_{y=0}^{\inf}p^2 \cdot(i - p)^{x+y} \cdot q^x \cdot (i - q) ^ y \cdot \binom{x+y}{x}

t = x+ y

=\sum_{t = 0}^{\inf}\sum_{x=0}^{t}p^2 \cdot(i - p)^{t} \cdot q^x \cdot (i - q) ^ {t - x}\cdot \binom{t}{x} =p^2\sum_{t = 0}^{\inf}(i - p)^{t}\sum_{x=0}^{t} \cdot \cdot q^x \cdot (i - q) ^ {t - x}\cdot \binom{t}{x}

据二项式定理:

(a+b)^n = \sum_{x = 0}^{n}a^x \cdot b^{(n - x)} \cdot \binom {n}{x}

得原式

=p^2\sum_{t = 0}^{\inf}(i - p)^{t} \cdot (q + 1 - q)^t =p^2\sum_{t = 0}^{\inf}(i - p)^{t}

发现此时的西格玛为等比数列, 运用等比数列求和公式

原式

=p^2 \cdot \dfrac{1-(1 - p)^{\inf + 1}}{1 - (1 - p)}

而分子 1-(1 - p)^{\inf + 1} 无限趋近于 1

\therefore \text{原式}=p^2 \cdot \dfrac{1}{p} = p
例8

检验矩阵 A \times B= C 是否成立

n \le 1000

竟然是MILLER_RABIN!

随机一个 n \times 1 的矩阵 D

A\times (B \times D) = C \times D

只要随机足够多的矩阵 D, 则一定正确.

O(n ^ 2)

完结撒花!!!!!!