算法基础-数学

算法基础-数学知识

  • ≡ \equiv :同余符号
  • 该文档所有用到的题目来自 ACWING,本文档作者 肥羊也 (acwing: 这个网站哟西嘚斯,CSDN:肥羊也),文档仅供学习交流,禁止商用。
  • 鸣谢:感谢 y y y 总讲解,从 y y y 总身上学到很多知识,喝水不忘挖井人,在这里向他以及 ACWING 上提供讲解的小伙伴,表示诚挚的感谢!
  • 重要知识目录:
    • 分解质因数
    • 筛法求质数
    • g c d gcd gcd
    • 欧拉函数
    • 快速幂
    • 扩展 g c d gcd gcd
    • 逆元
    • 高斯消元
    • 组合数
      • DP法
      • 预处理阶乘和逆元法
      • 卢卡斯定理
    • 容斥原理
    • 博弈论
      • NIM 游戏
      • SG 函数

质数

判断质数

  1. 从定义出发:对于一个数字 n,除了 1 和 本身 没有其他的因数。即2 ~ n-1没有任何一个数字可以整除 n。
  2. 试除法: 可以发现一个数的因数都是成对出现的,所以其实不用判断范围 2 ~ n-1,只需要判断 2 → n 2 \rightarrow \sqrt{n} 2n 即可。(代码有个小优化,我们判断是否到 n \sqrt{n} n 不用 sqrt 函数(可能有精度问题),也不用 i * i <= x可能有整数溢出问题,而是用 i < = x / i
#include <iostream>

using namespace std;

bool is_prime(int x) {
    for (int i = 2; i <= x / i; ++ i ) {
        if (x % i == 0) return false;
    }
    return true;
}

int main() {
    int n;
    cin >> n;
    while (n --) {
        int x;
        cin >> x;
        if (is_prime(x)) puts("Yes");
        else puts("No");
    }
    return 0;
}

分解质因数

  1. 朴素做法:先找到 2 ~ n的所有质数,然后试除法,如果 n % x == 0说明该数是一个质因数,就可以求得其指数。
  2. 试除法改进:从 2 开始直接向后遍历,如果 n 可以整除当前的数,说明当前的数是一个质因数,之后 n = n / i;直到 n 不能整除当前数,就得到了答案。
    • 合理性证明(反证法):因为 2 是质数,从 2 开始,每次把当前的数都除干净,如果当前数 i 不是质数,n 可以整除当前数 i ,每个数 x 都至少有一个质因子 y, y < = x y <= x y<=x,因为当前是合数,所以 y < x y < x y<x,假设 i 的质因子为 y,i 能整除 y,n 能整除 i,所以 n 能整除 y,且 y < i,表明前面有一个数没有整除干净,就矛盾了,因为我们每次都把当前数整出干净,所以这样的做法一定可以找到每个质因数。(由此可以得出,任何一个数都有如下结论: N = p 1 a 1 × p 2 a 2 × p 3 a 3 × . . . × p k a k ( p i 是 质 数 ) N = p_1^{a_1} \times p_2^{a_2} \times p_3^{a_3} \times ... \times p_k^{a_k} (p_i 是质数) N=p1a1×p2a2×p3a3×...×pkak(pi)
    • 补充:对于任何一个数,最多有一个大于 n \sqrt{n} n 的质因数,如果有两个则他们相乘就大于 N(由上面得出的结论)。所以对于任意一个数字,大于 n \sqrt{n} n 的部分完全可以不用循环来做,只需要一直整除到 n \sqrt{n} n ,如果最后的数字是 1 表明没有大于 n \sqrt{n} n 的整数,否则只需要把剩下的数字也作为答案输出即可。
#include <iostream>

using namespace std;

void get_prime(int x) {
    for (int i = 2; i <= x / i; ++ i) {
        if (x % i == 0) {
            int s = 0;
            while (x % i == 0) x /= i, s ++;
            cout << i << " " << s << endl;
        }
    }
    if (x > 1) cout << x << " 1" << endl;
    return;
}

int main() {
    int n;
    while (n --) {
        int x;
        cin >> x;
        get_prime(x);
    }
    return 0;
}

筛质数

筛出 1 - n 的所有质数:

  1. 最简单做法:对于每个数,都用试除法判断是否是质数。(没有利用到以及筛选出来的质数的信息)
  2. 筛法:顾名思义,筛法就像是在过滤 1 - n 中的数字,把不是质数的筛去,最后只剩下质数。
    1. 朴素筛法:每次循环到当前数,就把当前数的所有小于 n 的倍数都筛去。
    2. 埃氏筛法:每个数都至少有一个质因数,因此合数其实是没有必要进行筛的操作,只需要当前数如果是质数,就把当前数小于 n 的倍数都筛去。
    3. 线性筛法:每个数都让他被他的最小质因数筛去,保证每个数都只被筛一次。
#include <iostream>

using namespace std;

const int N = 1e6 + 10;

int primes[N], cnt;
bool st[N];

// 朴素筛法
void get_prime_0(int n) {
    for (int i = 2; i <= n; ++ i ) {
        if (!st[i]) primes[cnt ++] = i;
        for (int j = i + i; j <= n; j += i) st[j] = true;
    }
}

// 埃氏筛法 O(nloglogn)
void get_prime_1(int n) {
    for (int i = 2; i <= n; ++ i ) {
        if (!st[i]) {
            primes[cnt ++] = i;
            for (int j = i + i; j <= n; j += i)
                st[j] = true;
        }
    }
}

// 线性筛法
void get_prime(int n) {
    for (int i = 2; i <= n; ++ i ) {
        if (!st[i]) primes[cnt ++] = i;
        for (int j = 0; primes[j] <= n / i; ++ j ) {
            primes[primes[j] * i] = true;
            if (i % primes[j] == 0) break;
        }
    }
}

int main() {
    int n;
    cin >> n;
    get_prime(n);
    cout << cnt << endl;
    return 0;
}

约数

求约数

  1. 从 1 - n
  2. 1 − n 1 - \sqrt{n} 1n ,约数是成对存在的
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

void get_divisors(int x) {
    vector<int> res;
    for (int i = 1; i <= x / i; ++ i ) {
        if (x % i == 0) {
            res.push_back(i);
            if (i != x / i) res.push_back(x / i);
        }
    }
    sort(res.begin(), res.end());
    for (auto num : res) cout << num << " ";
    puts("");
    return;
}

int main() {
    int n;
    cin >> n;
    while (n --) {
        int x;
        cin >> x;
        get_divisors(x);
    }
    return 0;
}

求约数个数

由上面得到的结论: N = p 1 a 1 × p 2 a 2 × p 3 a 3 × . . . × p k a k ( p i 是 质 数 ) N = p_1^{a_1} \times p_2^{a_2} \times p_3^{a_3} \times ... \times p_k^{a_k} (p_i 是质数) N=p1a1×p2a2×p3a3×...×pkak(pi)

每个数的约数,可以由所有这些质数相乘,每个质数的次数可以在 [ 0 , k i ] [0, k_i] [0,ki] 之间任意选择,所以所有的约数的个数就是: ∏ i = 1 k a i + 1 \prod_{i =1}^{k} a_i + 1 i=1kai+1

  1. 分解质因数
  2. 次数相乘求解
#include <iostream>
#include <unordered_map>

using namespace std;

typedef long long LL;

const int mod = 1e9 + 7;

unordered_map<int, int> p;

int main() {
    int n;
    while(n --) {
        int a;
        cin >> a;
        
        for (int i = 2; i <= a / i; ++ i ) {
            while (a % i == 0) {
                a /= i;
                p[i] ++;
            }
        }
        if (a > 1) p[a] ++;
    }
    
    LL res = 1;
    for (auto num : p)  
        res = res * (nums.second + 1) % mod;
    cout << res << endl;
    return 0;
}

约数之和

我们知道所有约数的个数是由所有质数的组合,那么把每一种质数的组合加到一起就是约数之和,加到一起这个式子可以简化为: ∏ i = 1 k p i 0 + p i 1 + . . . + p i a i \prod_{i=1}^{k} p_i^0 + p_i^1 + ... + p_i^{a_i} i=1kpi0+pi1+...+piai

想要验证展开即可。

#include <iostream>
#include <unordered_map>

using namespace std;

typedef long long LL;

const int mod = 1e9 + 7;

unordered_map<int, int> p;

int main() {
    int n;
    cin >> n;
    while (n --) {
        int a;
        cin >> a;
        
        for (int i = 2; i <= a / i; ++ i ) {
            while (a % i == 0) {
                a /= i;
                p[i] ++;
            }
        }
        if (a > 1) p[a] ++;
    }
    LL res = 1;
    for (auto num : p) {
        LL a = p.first, b = p.second;
        LL t = 1;
        while (b --) t = (t * a + 1) % mod;
        res = res * t % mod;
    }
    cout << res << endl;
    return 0;
}

最大公约数

  1. 求两个数分别的因数,然后找相同的最大的
  2. 辗转相除法
#include <iostream>

using namespace std;

int gcd(int a, int b) {
    return b ? gcd(b, a % b) : a;
}

int main() {
    int n;
    cin >> n;
    while (n --) {
        int a, b;
        cin >> a >> b;
        cout << gcd(a, b) << endl;
    }
    return 0;
}

欧拉函数

1∼N 中与 N 互质的数的个数被称为欧拉函数,记为 ϕ(N)。

  1. 从 1 ~ N 中去掉所有 p 1 , p 2 , . . . p k p_1, p_2, ... p_k p1,p2,...pk 的倍数,然后重复以下操作:加上重复减去的,减去重复加上的。(容斥原理)最后公式可以化简为: ϕ ( N ) = N × ( 1 − 1 p 1 ) × ( 1 − 1 p 2 ) × ( 1 − 1 p 3 ) × . . . × ( 1 − 1 p k ) ϕ(N) = N \times (1 - \frac{1}{p_1}) \times (1 - \frac{1}{p_2}) \times (1 - \frac{1}{p_3}) \times ... \times (1 - \frac{1}{p_k}) ϕ(N)=N×(1p11)×(1p21)×(1p31)×...×(1pk1) 。(优化技巧,这里防止整数溢出做同样的处理: res = res / a * (a - 1)
  2. 筛法求欧拉函数
// 定义求欧拉函数
#include <iostream>

using namespace std;

int main() {
    int n;
    cin >> n;
    
    while (n -- ) {
        int a;
        cin >> a;
        
        int res = a;
        for (int i = 2; i <= a / i; ++ i ) {
            if (a % i == 0) {
                res = res / i * (i - 1);
                while (a % i == 0) a /= i;
            }
        }
        if (a > 1) res = res / a * (a - 1);
        cout << res << endl;
    }
    return 0;
}
// 筛法求欧拉函数
#include <iostream>

using namespace std;

typedef long long LL;

const int N = 1e6 + 10;

int primes[N], cnt;
int phi[N];
bool st[N];

void get_eulers(int n) {
    phi[1] = 1;
    
    for (int i = 2; i <= n; ++ i ) {
        if (!st[i]) {
            primes[cnt ++] = i;
            phi[i] = i - 1;
        }
        for (int j = 0; primes[j] <= n / i; ++ j ) {
            st[primes[j] * i] = true;
            if (i % primes[j] == 0) {
                phi[i * primes[j]] = phi[i] * primes[j];
                break;
            }
            phi[i * primes[j]] = phi[i] * (primes[j] - 1);
        }
    }
    
    LL res = 0;
    for (int i = 1; i <= n; ++ i) res += phi[i];
    cout << res << endl;
    return;
}

int main() {
    int n;
    cin >> n;
    get_eulers(n);
    return 0;
}

性质

若 a 与 n 互质,则 a ϕ ( n )   m o d   n = 1 a^{ϕ(n)} \ mod \ {n} = 1 aϕ(n) mod n=1

证明:

假设 1-n 中和 n 互质的数为 $a_1, a_2, a_3, ;…;, a_{ϕ(n)} $,因为 a 与 n 互质,则有 :
( a × a 1 ) × ( a × a 2 ) × ( a × a 3 ) ×    . . .    × ( a × a ϕ ( n ) ) ≡ a 1 × a 2 × a 3 ×    . . .    × a ϕ ( n ) ( m o d    n ) 即 : a ϕ ( n ) × a 1 × a 2 × a 3 ×    . . .    × a ϕ ( n ) ≡ a 1 × a 2 × a 3 ×    . . .    × a ϕ ( n ) ( m o d    n ) (a \times a_1) \times (a \times a_2) \times (a \times a_3) \times \;...\; \times (a \times a_{ϕ(n)}) \equiv \\ a_1 \times a_2 \times a_3 \times \;...\; \times a_{ϕ(n)} \quad( mod \; n) \\ \quad 即: \\ a^{ϕ(n)} \times a_1 \times a_2 \times a_3 \times \;...\; \times a_{ϕ(n)} \equiv \\ a_1 \times a_2 \times a_3 \times \;...\; \times a_{ϕ(n)} \quad( mod \; n) (a×a1)×(a×a2)×(a×a3)×...×(a×aϕ(n))a1×a2×a3×...×aϕ(n)(modn)aϕ(n)×a1×a2×a3×...×aϕ(n)a1×a2×a3×...×aϕ(n)(modn)

特别的: 当 n 为质数的时候,因为 n n n 的欧拉函数为 ( n − 1 ) (n - 1) (n1) 所以有 a n − 1   m o d   n = 1 a^{n - 1} \ mod \ {n} = 1 an1 mod n=1 (费马定理)

快速幂

  1. 快速幂
  2. 快速幂求逆元
    • 对于 a b \frac{a}{b} ba 我们希望找到一个数 x,使得 a b ≡ a × x ( m o d    m ) \frac{a}{b} \equiv a \times {x} \quad (mod \; m) baa×x(modm),则称 x 为 b 的模 m 的乘法逆元。b 存在乘法逆元的充分必要条件是,b 与 m 互质当 m 为 质数的时候 b m − 2 b ^ {m - 2} bm2 即为逆元(费马定理)。
// 快速幂
#include <iostream>

using namespace std;

typedef long long LL;

int quick_power(LL a, LL b, LL p) {
    LL res = 1, t = a;
    while (b) {
        if (b & 1) res = res * t % p;
        t = t * t % p;
        b >>= 1;
    }
    return res % p;
}

int main() {
    int n;
    cin >> n;
    while (n -- ) {
        int a, b, p;
        cin >> a >> b >> p;
        cout << quick_power(a, b, p) << endl;
    }
    return 0;
}

// 快速幂求逆元
#include <iostream>

using namespace std;

int quick_power(int a, int k, int p) {
    int res = 1;
    while (k) {
        if (k & 1) res = 1ll * res * a % p;
        k >>= 1;
        a = 1ll * a * a % p;
    }
    return res;
}

int main() {
    int n;
    cin >> n;
    while (n -- ) {
        int a, p;
        cin >> a >> p;
        
        if (a % p) cout << quick_power(a, p - 2, p);
        else cout << "impossible" << endl;
    }
    return 0;
}

扩展欧几里得算法

裴蜀定理

  • 有一对正整数a、b,一定存在非零整数x、y使得 a x + b y = g c d ( a , b ) ax + by = gcd(a, b) ax+by=gcd(a,b)。且a,b的最大公约数一定是 a 和 b 能凑出来的最小的正整数。(a 是 gcd(a,b) 的倍数,b也是,所以 ax+by一定也是gcd(a,b)的倍数)

扩展欧几里得就是求x、y。

a 、 b a、b ab x 、 y x、y xy b 、 a      m o d      b b、 a\;\;mod\;\;b bamodb x ′ 、 y ′ x'、y' xy 的关系,因为 a 、 b a、b ab b 、 a      m o d      b b、 a\;\;mod\;\;b bamodb 的最大公约数相同,可以看作求 b 、 a      m o d      b b、 a\;\;mod\;\;b bamodb x ′ 、 y ′ x'、y' xy,再转化为 a 、 b a、b ab x 、 y x、y xy
b y    + ( a    m o d    b ) x = d a    m o d    b = a − ⌊ a b ⌋ × b 则 有 : a x + b ( y − ⌊ a b ⌋ × x ) = d by \; + (a\;mod\;b)x = d \\ a \; mod \; b = a - \lfloor \frac{a}{b} \rfloor \times b \\ 则有:\\ ax + b(y- \lfloor \frac{a}{b} \rfloor \times x) = d by+(amodb)x=damodb=aba×bax+b(yba×x)=d

#include <iostream>
#include <cstdio>

using namespace std;

int exgcd(int a, int b, int&x, int &y) {
    if (!b) {
        x = 1, y = 0;
        return a;
    }
    
    int d = exgcd(b, a % b, y, x);
    y -= a / b * x;
    return d;
}

int main() {
    int n;
    scanf("%d", &n);
    
    while (n -- ) {
        int a, b;
        scanf("%d %d", &a, &b);
        
        int x, y;
        exgcd(a, b, x, y);
        printf("%d %d\n", x, y);
    }
    return 0;
}

补充所有解的形式:
x = x 0 − b d × k y = y 0 + a d × k (    g c d ( a , b ) = d a n d k ∈ Z    ) x = x_0 - \frac{b}{d} \times k \\ y = y_0 + \frac{a}{d} \times k \\ (\; gcd(a, b) = d \quad and \quad k \in Z \;) x=x0db×ky=y0+da×k(gcd(a,b)=dandkZ)
所有解的形式的证明:代入 a x + b y = d ax + by = d ax+by=d

线性同余方程

求解 a x ≡ b ( m o d    m ) ax \equiv b \quad( mod \; m) axb(modm) 的 x:

相当于 a x = m y + b ax = my + b ax=my+b a x − m y = b ax - my = b axmy=b ,令 y = − y y = -y y=y,则 a x + m y = b ax + my = b ax+my=b 即扩展欧几里得。需要 b b b 必须是 g c d ( a , m ) gcd(a, m) gcd(a,m) 的整数倍,否则无解。

#include <iostream> 

using namespace std;

int exgcd(int a, int b, int& x, int& y) {
    if (!b) {
        x = 1, y = 0;
        return a;
    }
    int d = exgcd(b, a % b, y, x);
    y -= a / b * x;
    return d;
}

int main() {
    int n;
    scanf("%d", &n);
    
    while (n -- ) {
        int a, b, m;
        scanf("%d %d %d", &a, &b, &m);
        
        int x, y;
        int d = exgcd(a, m, x, y);
        if (b % d) puts("impossible");
        else printf("%d\n", 1ll * x * (b / d) % m);
    }
    return 0;
}

通解: x = x 0 + m g c d × k ( k ∈ Z ) x = x_0 + \frac{m}{gcd} \times k \quad (k \in Z) x=x0+gcdm×k(kZ)

对于得到的任意答案 x i    m o d    m g c d x_i \; mod \; \frac{m}{gcd} ximodgcdm 都是答案,同理 x i    m o d    m x_i \; mod \; m ximodm 也是答案,因为此时相当于上式 k = g c d k = gcd k=gcd 。本题最后结果对 m 取模,防止整数溢出,也得到了正确答案。

其他用途: 求解逆元,即 a x ≡ a b ( m o d      p , 且 p 非 质 数 , b    和    p    互 质 ) ax \equiv \frac{a}{b} \quad(mod \;\; p,且 p 非质数, b \; 和 \; p\; 互质) axba(modp,pbp)

a b x + p b y = a abx + pby = a abx+pby=a 求解扩展欧几里得即可。

p b y ′ + ( a b      m o d      p b ) pby' + (ab \;\;mod\;\; pb) pby+(abmodpb) 依次下去, x 、 y x、y xy 的系数都是减小的趋势。这里我感觉很中二啊,留一个标记,不知道有没有更好的解法。

中国剩余定理

中国剩余定理要求任意两个 m ,两两互质。则有:

x = a 1 × M 1 × M 1 − 1 + a 2 × M 2 × M 2 − 1 +    . . .    + a k × M k × M k − 1 ( M − 1 为 M 的 逆 元 ) x = a_1\times M_1 \times M_1^{-1} + a_2\times M_2 \times M_2^{-1} + \; ... \; + a_k\times M_k \times M_k^{-1} \quad (M^{-1} 为 M 的逆元) x=a1×M1×M11+a2×M2×M21+...+ak×Mk×Mk1(M1M)

对于任意两个 m 不能保证互质的,可以两两求解,最后一次求得的即为所有的解。首先根据前面的线性同余方程有如下结论:
a x + m y = b 等 价 于 : a x ≡ b ( m o d    m ) ax + my = b \\ 等价于:\\ ax \equiv b \quad(mod \; m) ax+my=baxb(modm)
要求解的问题如下:
x ≡ a 1 ( m o d    m 1 ) x ≡ a 2 ( m o d    m 2 ) x ≡ a 3 ( m o d    m 4 ) . . . x ≡ a k ( m o d    m k ) 问 题 即 为 求 解 满 足 题 意 的 x x \equiv a_1 \quad(mod \; m_1) \\ x \equiv a_2 \quad(mod \; m_2) \\ x \equiv a_3 \quad(mod \; m_4) \\ ... \\ x \equiv a_k \quad(mod \; m_k) \\ 问题即为求解满足题意的 x xa1(modm1)xa2(modm2)xa3(modm4)...xak(modmk)x

特别的,对于两个而言有:
x ≡ a 1 ( m o d    m 1 ) x ≡ a 2 ( m o d    m 2 ) 可 转 化 为 : x = k 1 × m 1 + a 1 x = k 2 × m 2 + a 2 则 有 : k 1 × m 1 − k 2 × m 2 = a 2 − a 1 x \equiv a_1 \quad(mod \; m_1) \\ x \equiv a_2 \quad(mod \; m_2) \\ 可转化为:\\ x = k_1 \times m_1 + a_1 \\ x = k_2 \times m_2 + a_2 \\ 则有: \\ k_1 \times m_1 - k_2 \times m_2 = a_2 - a_1 xa1(modm1)xa2(modm2)x=k1×m1+a1x=k2×m2+a2k1×m1k2×m2=a2a1
而上式可以用扩展欧几里得求出 k 1 、 k 2 k_1 、k_2 k1k2 ,其通项为:
k 1 = k ′ + k × m 2 d k 2 = k ′ ′ + k × m 1 d 而 且 需 要 满 足 前 提 条 件 : ( a 2 − a 1 )    m o d    g c d ( m 1 , m 2 ) = 0 k_1 = k^{'} + k \times \frac{m_2}{d} \\ k_2 = k^{''} + k \times \frac{m_1}{d} \\ 而且需要满足前提条件:\\ (a_2 - a_1) \; mod \; gcd(m_1, m_2) = 0 k1=k+k×dm2k2=k+k×dm1(a2a1)modgcd(m1,m2)=0
则有:
x = k 1 × m 1 + a 1 = ( k ′ + k × m 2 d ) × m 1 + a 1 = a 1 + k ′ × m 1 + k × m 2 × m 1 d = x 0 + k × m d = k × m d + x 0 = k × m ′ + a ′ 则 x 的 最 小 正 整 数 解 为 : ( a ′    m o d    m ′ + m ′ )    m o d    m ′ x = k_1 \times m_1 + a_1 \\ = (k^{'} + k \times \frac{m_2}{d}) \times m_1 + a_1 \\ = a_1 + k' \times m_1 + k \times \frac{m_2 \times m_1}{d} \\ = x_0 + k \times \frac{m}{d} \\ = k \times \frac{m}{d} + x_0 \\ = k \times m' + a' \\ 则 x 的最小正整数解为:\\ (a' \; mod \; m' + m') \; mod \; m' x=k1×m1+a1=(k+k×dm2)×m1+a1=a1+k×m1+k×dm2×m1=x0+k×dm=k×dm+x0=k×m+ax(amodm+m)modm
当 x 仍需满足其他 x = k i × m i + a i x = k_i \times m_i + a_i x=ki×mi+ai 时,只需用上次求得的 x = k × m ′ + a ′ x = k \times m' + a' x=k×m+a 重复使用扩展欧几里得即可,直到不再需要满足其他的式子,则最后输出 x x x 的最小正整数解。

#include <iostream>

using namespace std;

typedef long long LL;

LL exgcd(LL a, LL b, LL& x, LL& y) {
	if (!b) {
        x = 1, y = 0;
        return a;
	}
    
    LL d = exgcd(b, a % b, y, x);
    y -= a / b * x;
    return d;
}

int main() {
	int n;
    cin >> n;
    
    bool has_answer = true;
    LL m1, a1;
    cin >> m1 >> a1;
    while ( -- n ) {
        LL m2, a2;
        cin >> m2 >> a2;
        
        LL k1, k2;
        LL d = exgcd(m1, m2, k1, k2);
        
        if ((a1 - a2) % d) {
            has_answer = false;
            break;
        }
        
        k1 *= (a2 - a1) / d;
        LL t = m2 / d;
        k1 = (k1 % t + t) % t; // 防止下面有整数溢出情况
        
        a1 = a1 + k1 * m1; // 先使用 m1 避免 m1 被修改
        m1 = m1 / d * m2;
    }
    if (has_answer) cout << (a1 % m1 + m1) % m1 << endl;
    else puts("-1");
    return 0;
}

高斯消元

高斯消元解线性方程组

  1. 把当前列 c c c 中找到最大一行 t t t,把他换到当前的最上面 r r r 行。如果 n u m [ t , c ] num[t, c] num[t,c] 0 0 0,则继续下一列;否则这行每一个数都除以 n u m [ t , c ] num[t, c] num[t,c] 使 n u m [ t . c ] num[t. c] num[t.c] 1 1 1
  2. n u m [ t ] num[t] num[t] 这行消去所有下面行 x x x n u m [ x , c ] num[x, c] num[x,c] 使之为 0 0 0。之后移动到下一行 t + 1 t + 1 t+1、下一列 c + 1 c + 1 c+1,重复这两个步骤直到结束。
  3. 如果 r < n r < n r<n 可能有两种情况无穷多解或者无解,只要增广矩阵出现 0 ! = 0 0 != 0 0!=0 的情况就是无解。
  4. 否则有解,从下往上,把每一行除了第一个数之外其他的数都减为 0 0 0,之后最后一列就是答案。
#include <iostream>
#include <cmath>

using namespace std;

const double eps = 1e-6;
const int N = 110;

int n;
double matrix[N][N];

int gauss() {
    int r = 0, c = 0;
    for (; c < n; ++ c ) {
        int t = r;
        for (int i = r + 1; i < n; ++ i ) {
            if (fabs(matrix[i][c]) > fabs(matrix[t][c])) t = i;
        }
        
        if (fabs(matrix[t][c]) < eps ) continue;
        
        if (t != r) 
            for (int i = c; i < n + 1; ++ i ) 
                swap(matrix[t][i], matrix[r][i]);
        
        for (int i = n; i >= c; i -- ) matrix[r][i] /= matrix[r][c];
        
        for (int i = r + 1; i < n; ++ i ) {
            if (fabs(matrix[i][c]) > eps) {
                for (int j = n; j >= c; j -- ) {
                    matrix[i][j] -= matrix[r][j] * matrix[i][c];
                }
            }
        }
        
        r ++;
    }
    
    if (r < n) {
        for (int i = r; i < n; ++ i ) 
            if (matrix[i][n]) return -1;
        return 1;
    }
    
    for (int i = n - 1; i >= 0; i -- ) {
        for (int j = i + 1; j < n; j ++ ) {
            matrix[i][n] -= matrix[i][j] * matrix[j][n];
        }
    }
    return 0;
}

int main() {
    cin >> n;
    
    for (int i = 0; i < n; ++ i ) {
        for (int j = 0; j < n + 1; ++ j ) cin >> matrix[i][j];
    }
    
    int res = gauss();
    if (res > 0) puts("Infinite group solutions");
    else if (res < 0) puts("No solution");
    else {
        for (int i = 0; i < n; ++ i ) printf("%.2lf\n", matrix[i][n]);
    }
    return 0;
}

高斯消元解异或线性方程组

复习一遍高斯消元得流程

  1. 找到非零(非1),并交换,找不到就下一列
  2. 消去下面所有非零(非1),枚举下一列、下一行
  3. 如果 r < n,看看从 r 开始后面有没有 0 != 0 的情况,有就是无解,否则无穷多组解
  4. 否则倒着减去相应的值,求解答案,最后输出
#include <iostream>

using namespace std;

const int N = 110;

int n;
int a[N][N];

int gauss() {
    int r, c;
    for (r = c = 0; c < n; ++ c) {
        
        int t = r;
        for (int i = r + 1; i < n; ++ i ) {
            if (a[i][c]) {
                t = i;
                break;
            }
        }
        
        if (!a[t][c]) continue;
        
        for (int i = c; i < n + 1; ++ i ) swap(a[r][i], a[t][i]);
        
        for (int i = r + 1; i < n; ++ i ) {
            if (a[i][c]) {
                for (int j = c; j < n + 1; ++ j ) {
                    a[i][j] ^= a[r][j];
                }
            }
        }
        
        r ++;
    }
    
    if (r < n) {
        for (int i = r; i < n; ++ i) {
            if (a[i][n]) return -1;
        }
        return 1;
    }
    
    for (int i = n - 1; i >= 0; i -- ) {
        for (int j = i + 1; j < n; ++ j ) {
            a[i][n] ^= a[i][j] & a[j][n];
        }
    }
    return 0;
}


int main() {
    scanf("%d", &n);
    
    for (int i = 0; i < n; ++ i ) {
        for (int j = 0; j < n + 1; ++ j ) {
            scanf("%d", &a[i][j]);
        }
    }    
    
    int res = gauss();
    if (res < 0) puts("No solution");
    else if (res > 0) puts("Multiple sets of solutions");
    else {
        for (int i = 0; i < n; ++ i ) cout << a[i][n] << endl;
    }
    return 0;
}

组合数

推导法求解

思想:类似 DP,可以推导 C i j = C i − 1 j + C i − 1 j − 1 C_{i}^{j} = C_{i - 1}^{j} + C_{i - 1}^{j - 1} Cij=Ci1j+Ci1j1

对于 1 0 3 10^3 103 的数据,预处理一下 O ( 1 0 6 ) O(10^6) O(106),再输出是可以接受的。

#include <iostream>

using namespace std;

const int N = 2010;
const int mod = 1e9 + 7;

int c[N][N];

void init() {
    for (int i = 0; i < N; ++ i ) {
        for (int j = 0; j <= i; ++ j) {
            if (!j) c[i][j] = 1;
            else c[i][j] = (c[i - 1][j - 1] + c[i - 1][j]) % mod;
        }
    }
}

int read() {
    int res = 0;
    char ch = getchar();
    while (ch >= '0' && ch <= '9') {
        res = res * 10 + (ch - '0');
        ch = getchar();
    }
    return res;
}

int main() {
    init();
    
    int n;
    n = read();

    while (n -- ) {
        int a, b;
        a = read();
        b = read();
        cout << c[a][b] << endl;
    }
    return 0;
}

预处理阶乘和逆元

求逆元: a y = a ∗ x ( m o d      p ) \frac{a}{y} = a * x \quad(mod\;\; p) ya=ax(modp) x x x y y y m o d      p mod\;\; p modp 的逆元。当 p p p 是质数, y p − 2 y^{p-2} yp2 就是 y y y 的一个逆元。

本题我们需要求的是阶乘的逆元,所以逆元需要累乘。

#include <iostream>

using namespace std;

typedef long long LL;

const int N = 100010, mod = 1e9 + 7;

int fact[N], infact[N];
// i 的阶乘,以及 i 的阶乘的逆元

int read() {
    int res = 0;
    char ch = getchar();
    while (ch <= '9' && ch >= '0') {
        res = res * 10 + (ch - '0');
        ch = getchar();
    }
    return res;
}

int quick_power(int a, int k, int p) {
    int res = 1;
    while (k) {
        if (k & 1) res = 1ll * res * a % p;
        k >>= 1;
        a = 1ll * a * a % p;
    }
    return res;
}

void init() {
    fact[0] = infact[0] = 1;
    for (int i = 1; i < N; ++ i ) {
        fact[i] = 1ll * fact[i - 1] * i % mod;
        infact[i] = 1ll * infact[i - 1] * quick_power(i, mod - 2, mod) % mod;
    }
}

int main() {
    init();
    
    int n;
    n = read();
    
    while (n -- ) {
        int a, b;
        a = read();
        b = read();
        printf("%d\n", 1ll * fact[a] * infact[a - b] % mod * infact[b] % mod);
    }
    return 0;
}

卢卡斯定理

不得不感叹卢卡斯定理的证明实在是太妙了!

用到的两个等式

这里证明不再赘述。只要展开左边观察每一项即可得证。
( 1 + x ) p ≡ ( 1 + x p ) ( m o d      p ) ( 1 + x ) p a ≡ ( 1 + x p a ) ( m o d      p ) (1 + x)^ p \equiv (1 + x^p) \quad(mod \;\;p) \\ (1 + x)^ {p^{a}} \equiv (1 + x^ {p^{a}}) \quad(mod \;\;p) \\ 1+xp(1+xp)(modp)1+xpa(1+xpa)(modp)

证明1

首先我们可以把 a 、 b a、b ab 转化为 p p p 进制数:

a = a k ⋅ p k + a k − 1 ⋅ p k − 1 +    . . .    + a 1 ⋅ p 1 + a 0 ⋅ p 0 a = a_{k}\cdot p^{k} + a_{k - 1}\cdot p^{k - 1} +\;...\;+ a_{1}\cdot p^{1} + a_{0}\cdot p^{0} a=akpk+ak1pk1+...+a1p1+a0p0

b = b k ⋅ p k + b k − 1 ⋅ p k − 1 +    . . .    + b 1 ⋅ p 1 + b 0 ⋅ p 0 b = b_{k}\cdot p^{k} + b_{k - 1}\cdot p^{k - 1} +\;...\;+ b_{1}\cdot p^{1} + b_{0}\cdot p^{0} b=bkpk+bk1pk1+...+b1p1+b0p0

则有: ( 1 + x ) a = ( 1 + x ) a k ⋅ p k + a k − 1 ⋅ p k − 1 +    . . .    + a 1 ⋅ p 1 + a 0 ⋅ p 0 (1 + x)^a = (1 + x)^{a_{k}\cdot p^{k} + a_{k - 1}\cdot p^{k - 1} +\;...\;+ a_{1}\cdot p^{1} + a_{0}\cdot p^{0}} (1+x)a=(1+x)akpk+ak1pk1+...+a1p1+a0p0

= ( 1 + x ) a k ⋅ p k ⋅ ( 1 + x ) a k − 1 ⋅ p k − 1 ⋅    . . .    ⋅ ( 1 + x ) a 1 ⋅ p 1 ⋅ ( 1 + x ) a 0 ⋅ p 0 = (1 + x)^{a_{k}\cdot p^{k}} \cdot (1 + x)^{a_{k - 1}\cdot p^{k - 1}} \cdot \;...\; \cdot (1 + x)^{a_{1}\cdot p^{1}} \cdot (1 + x)^{a_{0}\cdot p^{0}} =(1+x)akpk(1+x)ak1pk1...(1+x)a1p1(1+x)a0p0

≡ ( 1 + x p k ) a k ⋅ ( 1 + x p k − 1 ) a k − 1 ⋅    . . .    ⋅ ( 1 + x p 1 ) a 1 ⋅ ( 1 + x p 0 ) a 0 ( m o d      p ) \equiv (1 + x^{p^k})^{a_k}\cdot (1 + x^{p^{k-1}})^{a_{k - 1}}\cdot \;...\;\cdot (1 + x^{p^{1}})^{a_{1}}\cdot (1 + x^{p^{0}})^{a_{0}} \quad(mod \;\;p) (1+xpk)ak(1+xpk1)ak1...(1+xp1)a1(1+xp0)a0(modp)

我们想求得 C a b C^{b}_{a} Cab,其在等式左边即为 x b x^b xb 的系数。

在等式右边也为 x b x^{b} xb 的系数,由 b b b p p p 进制可得: x b = x b k ⋅ p k + b k − 1 ⋅ p k − 1 +    . . .    + b 1 ⋅ p 1 + b 0 ⋅ p 0 x^b = x^{b_{k}\cdot p^{k} + b_{k - 1}\cdot p^{k - 1} +\;...\;+ b_{1}\cdot p^{1} + b_{0}\cdot p^{0}} xb=xbkpk+bk1pk1+...+b1p1+b0p0

显然, x b k ⋅ p k x^{b_k\cdot p^k} xbkpk 项在 ( 1 + x p k ) a k (1 + x^{p^k})^{a_k} (1+xpk)ak 中是 C a k b k ⋅ x b k ⋅ p k C_{a_k}^{b_k}\cdot x^{b_k\cdot p^k} Cakbkxbkpk ,同理 x b k − 1 ⋅ p k − 1 x^{b_{k-1}\cdot p^{k-1}} xbk1pk1 项在 ( 1 + x p k − 1 ) a k − 1 (1 + x^{p^{k-1}})^{a_{k-1}} (1+xpk1)ak1 中是 C a k − 1 b k − 1 ⋅ x b k − 1 ⋅ p k − 1 C_{a_{k-1}}^{b_{k-1}}\cdot x^{b_{k-1}\cdot p^{k-1}} Cak1bk1xbk1pk1,同理可求得 x b x^b xb 的系数为: C a k b k ⋅ C a k − 1 b k − 1 ⋅    . . .    ⋅ C a 1 b 1 ⋅ C a 0 b 0 C_{a_k}^{b_k}\cdot C_{a_{k-1}}^{b_{k-1}}\cdot \;...\;\cdot C_{a_{1}}^{b_{1}}\cdot C_{a_{0}}^{b_{0}} CakbkCak1bk1...Ca1b1Ca0b0

因此可得: C a b ≡ C a k b k ⋅ C a k − 1 b k − 1 ⋅    . . .    ⋅ C a 1 b 1 ⋅ C a 0 b 0 ( m o d      p ) C^{b}_{a} \equiv C_{a_k}^{b_k}\cdot C_{a_{k-1}}^{b_{k-1}}\cdot \;...\;\cdot C_{a_{1}}^{b_{1}}\cdot C_{a_{0}}^{b_{0}} \quad(mod \;\;p) CabCakbkCak1bk1...Ca1b1Ca0b0(modp)

因为 a 、 b a、b ab p p p 进制数,所以有 a 0 = a      m o d      p a_0 = a \;\;mod\;\; p a0=amodp b 0 = b      m o d      p b_0 = b \;\;mod\;\; p b0=bmodp

我们让 a 、 b a、b ab p p p 进制中右移一位,即 ⌊ a p ⌋ 、 ⌊ b p ⌋ \lfloor \frac{a}{p} \rfloor、\lfloor \frac{b}{p} \rfloor papb

对于 ⌊ a p ⌋ 、 ⌊ b p ⌋ \lfloor \frac{a}{p} \rfloor、\lfloor \frac{b}{p} \rfloor papb 重复上面最开始的步骤,同样可以得到: C ⌊ a p ⌋ ⌊ b p ⌋ ≡ C a k b k ⋅ C a k − 1 b k − 1 ⋅    . . .    ⋅ C a 1 b 1 ( m o d      p ) C^{\lfloor \frac{b}{p} \rfloor}_{\lfloor \frac{a}{p} \rfloor} \equiv C_{a_k}^{b_k}\cdot C_{a_{k-1}}^{b_{k-1}}\cdot \;...\;\cdot C_{a_{1}}^{b_{1}} \quad(mod \;\;p) CpapbCakbkCak1bk1...Ca1b1(modp)

因此: C a b ≡ C ⌊ a p ⌋ ⌊ b p ⌋ ⋅ C a      m o d      p b      m o d      p ( m o d      p ) C^{b}_{a} \equiv C^{\lfloor \frac{b}{p} \rfloor}_{\lfloor \frac{a}{p} \rfloor}\cdot C_{ a \;\;mod\;\; p}^{ b \;\;mod\;\; p} \quad(mod \;\;p) CabCpapbCamodpbmodp(modp)

综上得证

证明2

假设:
{ ⌊ a p ⌋ = q a ⌊ b p ⌋ = q b ,      { a      m o d      p = r a b      m o d      p = r b \begin{cases} \lfloor \frac{a}{p} \rfloor = q_a \\ \lfloor \frac{b}{p} \rfloor = q_b \end{cases} , \;\; \begin{cases} a \;\;mod \;\; p = r_a \\ b \;\;mod \;\; p = r_b \end{cases} {pa=qapb=qb,{amodp=rabmodp=rb

所以有:
{ a = q a × p + r a b = q b × p + r b 因 为 ( 二 项 式 定 理 ) : ( 1 + x ) a = ∑ k = 0 a C a k ⋅ x k 且 : ( 1 + x ) a = ( 1 + x ) q a ⋅ p + r a = ( 1 + x ) q a ⋅ p ⋅ ( 1 + x ) r a ≡ ( 1 + x p ) q a ⋅ ( 1 + x ) r a ( m o d      p ) ( 因 为    ( 1 + x ) p ≡ ( 1 + x p ) ( m o d      p ) ) = ∑ i = 0 q a C q a   i ⋅ x i ⋅ p ⋅ ∑ j = 0 r A C r a   j ⋅ x j = ∑ i = 0 q a ∑ j = 0 r A C q a   i ⋅ C r a   j ⋅ x i ⋅ p + j \begin{cases} a = q_a \times p + r_a \\ b = q_b \times p + r_b \\ \end{cases} \\ 因为(二项式定理): (1 + x) ^ a = \sum^a_{k = 0} C_a^{k} \cdot x^k \\ 且: (1 + x) ^ a = (1 + x)^{q_a \cdot p + r_a} \\ = (1 + x)^{q_a \cdot p} \cdot (1 + x) ^ {r_a} \\ \equiv (1 + x^p)^{q_a} \cdot (1 + x) ^{r_a} \quad(mod \;\;p) \\ (因为\;(1 + x)^ p \equiv (1 + x^p) \quad(mod \;\;p)) \\ = \sum^{q_a}_{i = 0} C^{\ i}_{q_a} \cdot x^{i \cdot p} \cdot \sum^{r_A}_{j = 0} C^{\ j}_{r_a} \cdot x^{j} \\ = \sum^{q_a}_{i = 0} \sum^{r_A}_{j = 0}C^{\ i}_{q_a} \cdot C^{\ j}_{r_a} \cdot x^{i \cdot p + j} {a=qa×p+rab=qb×p+rb(1+x)a=k=0aCakxk(1+x)a=(1+x)qap+ra=(1+x)qap(1+x)ra(1+xp)qa(1+x)ra(modp)(1+xp(1+xp)(modp))=i=0qaCqa ixipj=0rACra jxj=i=0qaj=0rACqa iCra jxip+j

k = i ⋅ p + j k = i \cdot p + j k=ip+j,则 i = ⌊ k p ⌋ , j = k      m o d      p i = \lfloor \frac{k}{p} \rfloor,\quad j = k \;\;mod \;\; p i=pk,j=kmodp,所以上式等于:
= ∑ k = 0 a C q a ⌊ k p ⌋ ⋅ C r a   k      m o d      p ⋅ x k = \sum^a_{k = 0} C^{\lfloor \frac{k}{p} \rfloor}_{q_a} \cdot C^{\ k \;\;mod \;\; p}_{r_a} \cdot x^{k} =k=0aCqapkCra kmodpxk
综上有:
( 1 + x ) a ≡ ∑ k = 0 a C q a ⌊ k p ⌋ ⋅ C r a   k      m o d      p ⋅ x k ( m o d      p ) 且 : ( 1 + x ) a = ∑ k = 0 a C a k ⋅ x k 故 : ∑ k = 0 a C a k ⋅ x k ≡ ∑ k = 0 a C q a ⌊ k p ⌋ ⋅ C r a   k      m o d      p ⋅ x k ( m o d      p ) (1 + x) ^ a \equiv \sum^a_{k = 0} C^{\lfloor \frac{k}{p} \rfloor}_{q_a} \cdot C^{\ k \;\;mod \;\; p}_{r_a} \cdot x^{k} \quad(mod \;\;p) \\ 且: (1 + x) ^ a = \sum^a_{k = 0} C_a^{k} \cdot x^k \\ 故:\sum^a_{k = 0} C_a^{k} \cdot x^k \equiv \sum^a_{k = 0} C^{\lfloor \frac{k}{p} \rfloor}_{q_a} \cdot C^{\ k \;\;mod \;\; p}_{r_a} \cdot x^{k} \quad(mod \;\;p) \\ (1+x)ak=0aCqapkCra kmodpxk(modp)(1+x)a=k=0aCakxkk=0aCakxkk=0aCqapkCra kmodpxk(modp)
对于其中某一项 k = b k = b k=b 有:
C a b ≡ C q a ⌊ b p ⌋ ⋅ C r a   b      m o d      p ( m o d      p ) 即 : C a b ≡ C ⌊ a p ⌋ ⌊ b p ⌋ ⋅ C   a      m o d      p   b      m o d      p ( m o d      p ) 得 证 C_a^{b} \equiv C^{\lfloor \frac{b}{p} \rfloor}_{q_a} \cdot C^{\ b \;\;mod \;\; p}_{r_a} \quad (mod \;\;p) \\ 即:\\ C_a^{b} \equiv C^{\lfloor \frac{b}{p} \rfloor}_{\lfloor \frac{a}{p} \rfloor} \cdot C^{\ b \;\;mod \;\; p}_{\ a \;\;mod \;\; p} \quad (mod \;\;p) \\ 得证 CabCqapbCra bmodp(modp)CabCpapbC amodp bmodp(modp)

#include <iostream>

using namespace std;

typedef long long LL;

int readInt() {
    int res = 0;
    char ch = getchar();
    while (ch <= '9' && ch >= '0') {
        res =res * 10 + ch - '0';
        ch = getchar();
    }
    return res;
}

LL readLong() {
    LL res = 0;
    char ch = getchar();
    while (ch <= '9' && ch >= '0') {
        res = res * 10 + ch - '0';
        ch = getchar();
    } 
    return res;
}

int quick_power(int a, int k, int p) {
    int res = 1;
    while (k) {
        if (k & 1) res = 1ll * res * a % p;
        k >>= 1;
        a = 1ll * a * a % p;
    }
    return res;
}


int C(int a, int b, int p) {
    int res = 1;
    for (int i = 1, j = a; i <= b; i ++, j --) {
        res = 1ll * res * j % p;
        res = 1ll * res * quick_power(i, p - 2, p) % p;
    }
    return res;
}

int lucas(LL a, LL b, int p) {
    if (a < p && b < p) return C(a, b, p);
    return 1ll * C(a % p, b % p, p) * lucas(a / p, b / p, p) % p;
} 

int main() {
    int n;
    n = readInt();
    
    while (n -- ) {
        LL a, b;
        int p;
        a = readLong(), b = readLong(), p = readInt();
        // cout << a << " " << b << " " << p << endl;
        cout << lucas(a, b, p) << endl;
    }
    return 0;
}

高精度 + 分解质因数

为什么可以这样做呢?

:每个数都可以写成这样的形式, N = p 1 a 1 × p 2 a 2 × p 3 a 3 × . . . × p k a k ( p i 是 质 数 ) N = p_1^{a_1} \times p_2^{a_2} \times p_3^{a_3} \times ... \times p_k^{a_k} (p_i 是质数) N=p1a1×p2a2×p3a3×...×pkak(pi) 。所以 n ! n! n! 只需要用 ⌊ n p ⌋ ⌊\frac{n}{p}⌋ pn 即可得到阶乘中某个质数的次数 p k p^k pk

#include <iostream>
#include <vector>

using namespace std;

const int N = 5010;

int primes[N], cnt;
bool st[N];
int sum[N];

int read() {
    int res = 0;
    char ch = getchar();
    while (ch <= '9' && ch >= '0') {
        res = res * 10 + ch - '0';
        ch = getchar();
    }
    return res;
}

void get_primes(int n) {
    for (int i = 2; i <= n; ++ i ) {
        if (!st[i]) primes[cnt ++] = i;
        for (int j = 0; primes[j] <= n / i; ++ j ) {
            st[primes[j] * i] = true;
            if (i % primes[j] == 0) break;
        }
    }
}

int get(int n, int p) {
    int res = 0;
    while (n) {
        res += n / p;
        n /= p;
    }
    return res;
}

void mul(vector<int>& a, int b) {

    int t = 0;
    for (int i = 0; i < a.size(); ++ i ) {
        t += a[i] * b;
        a[i] = t % 10;
        t /= 10;
    }

    while (t) {
        a.push_back(t % 10);
        t /= 10;
    }
    return;
}

int main() {
    int a, b;
    a = read(), b = read();

    get_primes(a);
    for (int i = 0; i < cnt; ++ i ) {
        sum[i] = get(a, primes[i]) - get(b, primes[i]) - get(a - b, primes[i]);
    }

    vector<int> res;
    res.push_back(1);
    for (int i = 0; i < cnt; ++ i ) {
        for (int j = 0; j < sum[i]; ++ j) {
            mul(res, primes[i]);
        }
    }

    for (int i = res.size() - 1; i >= 0; i -- ) printf("%d", res[i]);
    puts("");
    return 0;
}

卡特兰数

每一个越过中线走到距离中线最近的不合法路径上 l 2 l_2 l2 的不合法路径 l 1 l_1 l1,都可以把 l 1 l_1 l1 l 2 l_2 l2 的交叉点处到终点位置的路径 l 3 l_3 l3 沿着中线 y = x y = x y=x 轴对称,终点始终为 ( n − 1 , n + 1 ) (n - 1, n + 1) (n1,n+1)。即每一条不合法路径都可以转化成到 ( n − 1 , n + 1 ) (n - 1, n + 1) (n1,n+1) 的一条路径(同理,因为 原点到 ( n − 1 , n + 1 ) (n - 1, n + 1) (n1,n+1) 必须经过中线,一定和中线有交点,也一定可以转化成一条到 ( n , n ) (n, n) (n,n) 的非法路径)。

因此答案为: C 2 ⋅ n n − C 2 ⋅ n n − 1 C^{n}_{2\cdot n} - C^{n - 1}_{2\cdot n} C2nnC2nn1

可化简为 C 2 ∗ n n n + 1 \frac{C_{2*n}^{n}}{n + 1} n+1C2nn

#include <iostream>

using namespace std;

typedef long long LL;

const int mod = 1e9 + 7;

int read() {
    int res = 0;
    char ch = getchar();
    while (ch <= '9' && ch >= '0') {
        res = res * 10 + ch -'0';
        ch = getchar();
    }
    return res;
}

int quick_power(int a, int k, int p) {
    int res = 1;
    while (k) {
        if (k & 1) res = 1ll * res * a % p;
        k >>= 1;
        a = 1ll * a * a % p;
    }
    return res;
}

int main() {
    int n;
    n = read();
    
    int res = 1;
    for (int i = 2*n, j = 1; j <= n; i --, j ++) {
        res = 1ll * res * i % mod;
        res = 1ll * res * quick_power(j, mod - 2, mod) % mod;
    }
    
    res = 1ll * res * quick_power(n + 1, mod - 2, mod) % mod;
    
    cout << res << endl;
    return 0;
    
}

容斥原理

所有方案数为:所有单个集合的总方案数 所有两个集合的组合中重复加的数 所有三个集合的组合中重复减去的数 ·····

本题中所有单个集合的总方案项数 C m 1 C_{m}^{1} Cm1,所有两个集合的组合中重复加的项数 C m 2 C_{m}^{2} Cm2,······

总共要加减多少项呢? 因为除了所有的集合都不选之外,其他所有任意种组合我们都选了,所以总共组合的项数为 2 n − 1 2^{n} - 1 2n1 (所以有, ∑ i = 1 m C m i = 2 n − 1 \sum_{i = 1}^{m}C_{m}^{i} = 2^n - 1 i=1mCmi=2n1)。对于每一种组合如果他包含奇数个单个集合,我们就应该加上这个这种组合的方案数,否则减去。

每一项如何计算呢? 我们可以用位运算枚举每一种组合,记录一下每一种组合的值 v a l u e value value(当且仅当他小于 n n n 才算有效组合),那么这种组合的方案数为 ⌊ n v a l u e ⌋ \lfloor \frac{n}{value} \rfloor valuen

#include <iostream>

using namespace std;

const int N = 20;

int p[N];

int read() {
    int res = 0;
    char ch = getchar();
    while (ch <= '9' && ch >= '0') {
        res = res * 10 + ch - '0';
        ch = getchar();
    }
    return res;
}


int main() {
    int n, m;
    n = read();
    m = read();
    
    for (int i = 0; i < m; ++ i ) p[i] = read();
    
    int res = 0;
    for (int i = 1; i < 1 << m; ++ i ) {
        int t = 1, cnt = 0;
        for (int j = 0; j < m; ++ j ) {
            if (i & 1 << j) {
                if (1ll * t * p[j] > n) {
                    t = -1;
                    break;
                }
                cnt ++;
                t = t * p[j];
            }
        }
        
        if (t != -1) {
            if (cnt % 2) res += n / t;
            else res -= n / t;
        }
    }
    
    cout << res << endl;
    return 0;
}

博弈论

NIM游戏

  • (先手)必胜状态:如果经过某种操作,给对手操作的时候,对手是必败状态,则是必胜状态。
  • (先手)必败状态:无论如何操作,一定会输的状态。

由题可知,必败状态是所有堆为 0,其异或和为 0。当异或和为 0 无论如何操作都是异或和不为 0 的状态(因为 不可以不拿);当异或和不为 0 的时候,设异或和值为 x x x x x x 的二进制最高位为第 k k k 位,则一定有一堆的第 k k k 位为 1,设其数量为 a i a_i ai ,在 a i a_i ai 中拿去 a i − ( a i ⨁ x ) a_i - (a_i \bigoplus x) ai(aix) 个,则该堆变为 a i ⨁ x a_i \bigoplus x aix 个,所有堆的异或和就会变为 0。所以,当异或和不为 0 的时候,总可以让对手面临异或和为 0 的状态,且数量一直在减少,对手一定会遇到全为 0 的状态。因此只要先手遇到异或和不为 0 ,必胜,否则必败。

#include <iostream>

using namespace std;

int read() {
    int res = 0;
    char ch = getchar();
    while (ch <= '9' && ch >= '0') {
        res = res * 10 + ch - '0';
        ch = getchar();
    }
    return res;
}

int main() {
    int n;
    n = read();
    
    int res = 0;
    for (int i = 0; i < n; ++ i ) res = res ^ read();
    if (res) puts("Yes");
    else puts("No");
    return 0;
}

台阶 NIM游戏

可以只看奇数台阶,那么就是经典的 NIM游戏:

  • 如果从偶数台阶向下拿到 i i i,那么我们就把 i i i 上的刚刚新加的拿下去,相当于对奇数台阶没有进行操作
  • 如果那的是奇数台阶,就可以考虑永远让对手面临异或和为 0 0 0 的状态,则就可以胜利
#include <iostream>

using namespace std;

int main() {
    int n;
    cin >> n;
    
    int res = 0;
    for (int i = 0; i < n; ++ i ) {
        int x;
        cin >> x;
        
        if (i % 2 == 0) res ^= x;
    }
    
    if (res) puts("Yes");
    else puts("No");
    return 0;
}

集合 NIM 游戏

SG 函数

  • S G ( 0 ) = 0 SG(0) = 0 SG(0)=0 必败状态
  • S G ( x ) = m a x { S G ( y 1 ) , S G ( y 2 ) ,    . . .    , S G ( y k ) } ( m a x 是 求 不 存 在 的 最 小 自 然 数 ) SG(x) = max\{SG(y_1), SG(y_2), \;...\;, SG(y_k)\} \quad(max 是求不存在的最小自然数) SG(x)=max{SG(y1),SG(y2),...,SG(yk)}(max)

**SG函数的意义:**当有多个图的时候,我们可以把每个图的起点值异或起来,然后用 NIM游戏 的方法得到必胜和必败态。

为了防止运算 sg 达到指数级别,这里使用了**记忆化搜索**, 即把中间的结果保存下来。

// 对于数据不规范的少用自己写的 read
#include <iostream>
#include <unordered_set>
#include <cstring>

using namespace std;

const int N = 110, M = 10010;

int s[N], f[M];
int n, k;

int sg(int x) {
    if (f[x] != -1) return f[x];
    unordered_set<int> state;
    for (int i = 0; i < k; ++ i ) {
        if (x >= s[i]) state.insert(sg(x - s[i])); 
        // 如果可以采用这个拿取得方案,就求一下拿取之后得剩余得数量得 sg 函数。
        // 当前 sg 函数就是看看当前不能走到得最小的自然数。
    }
    
    for (int i = 0; ; ++ i ) {
        if (!state.count(i))
            return f[x] = i;
    }
}

int main() {
    cin >> k;
    for (int i = 0; i < k; ++ i ) cin >> s[i];
    cin >> n;
    
    int res = 0;
    memset(f, -1, sizeof f);
    for (int i = 0; i < n; ++ i ) {
        int x;
        cin >> x;
        res = res ^ sg(x);
    }
    
    if (res) puts("Yes");
    else puts("No");
    return 0;
}

拆分 NIM游戏

有一个很强的性质: S G ( b 1 , b 2 ) = S G ( b 1 )    ⨁    S G ( b 2 ) SG(b_1, b_2) = SG(b_1) \;\bigoplus\; SG(b_2) SG(b1,b2)=SG(b1)SG(b2)

把每一种可以拆分的局面的 S G SG SG 值都统计一下即可(且, i i i j j j 的值交换没有意义)。

#include <iostream>
#include <cstring>
#include <unordered_set>

using namespace std;

const int N = 110;

int n;
int f[N];

int sg(int x) {
    if (f[x] != -1) return f[x];
    
    unordered_set<int> state;
    for (int i = 0; i < x; ++ i ) {
        for (int j = 0; j <= i; ++ j ) {
            state.insert(sg(i) ^ sg(j));
        }
    }
    
    for (int i = 0; ; ++ i ) {
        if (!state.count(i)) {
            return f[x] = i;
        }
    }
}

int main() {
    cin >> n;
    
    int res = 0;
    memset(f, -1, sizeof f);
    for (int i = 0; i < n; ++ i ) {
        int x;
        cin >> x;
        res ^= sg(x);
    }
    if (res) puts("Yes");
    else puts("No");
    
    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

肥羊也

感谢给肥羊投喂!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值