ACM数论笔记备份

杂项

Nim游戏

石子型:有n堆石子,甲乙交替取,每人每次只能从任意一堆中取至少一枚,无法取的人输

结论: 初态为$ a_1 \ xor \ a_2 \ xor \ … \neq 0$的时候先手必胜

先手第一步怎么样取: 先得到所有的a的异或和S,如果a[i] ^ S 的值小于a[i](即说明s中的最高位和a[i]中的那一位都为1,那么将a[i] 变为 a[i] ^ S即可

台阶型: 有1 ~ n级台阶,第i级上摆放a[i]个石子每次可以把k级的石子移动至少一个到k - 1级,0级的石子不能再移动, 无法移动石子则输

结论: a 1 a_1 a1 ^ a 3 a_3 a3 ^ $ a_5$… $\neq 0 $的时候先手必胜

证明关键点:所有的石子都必定会经过1级到0级, 对于甲的第一步,将奇数台阶上的下移到偶数台阶,无异于直接拿走石子,因为甲乙可以对称的完成该操作

甲的取法: 先把奇数台阶上的石子下移到偶数台阶,使上式值为0

如果乙把偶数台阶上的移到奇数台阶使得 s ≠ 0 s \neq 0 s=0,则甲再把这一堆再下移到偶数

如果乙把奇数台阶上的移到偶数台阶使得 s ≠ 0 s \neq 0 s=0,则甲也下移某奇数台阶上的石子到偶数台阶

有向图游戏和SG函数

m e x mex mex运算 ( m i n i m u m    e x c l u s i o n ) (minimum\ \ exclusion) (minimum  exclusion) m e x ( S ) mex(S) mex(S)为不属于S集合中的最小非负整数

SG函数:

设结点(状态)x有k个子节点(后继状态)$y_1, y_2, y_3...y_k$

那么$SG(x) = mex\{SG(y_1), SG(y_2)... SG(y_k)\}$

叶子结点的SG值为0

SG定理:

由n个有向图组成的组合游戏设起点分别为$s_1, s_2...s_n$当

$SG(s_1)$ ^ $SG(s_2)$^ ... ^ $SG(s_n)$ $\neq 0$先手必胜

给定一个有n个结点和m条边的有向无环图,和k个棋子所在的结点编号。

两名玩家交替移动棋子,每次只能把一颗沿着有向边移动一步,无法移动者失败

对于这k个棋子,每个棋子都是独立的,可以把这些棋子拆分成k个有向图游戏

  1. 有向图无环上的棋子游戏
int h[N], to[M], ne[M], idx;
int f[N];
void add(int a, int b) {
    to[idx] = b, ne[idx] = h[a], h[a] = idx++;
}  
int sg(int u) {
    if (f[u] != -1) return f[u];
    unordered_set<int> s;
    for (int i = h[u]; ~i; i = ne[i]) 
        s.emplace(sg(to[i]));
    for (int i = 0; ;i++) 
        if (!s.count(i)) return f[u] = i;
}
int main() {
    cin >> n >> m >> k;
    for (int i = 0; i < m; i++) {
        cin >> a >> b; add(a, b);
    }
    memset(f, -1, sizeof(f));
    memset(h, -1, sizeof(f));
    int res = 0;
    for (int i = 0; i < k; i++) {
        cin >> x;
        res ^= sg(x);
    }
    if (res) puts("win");
    else puts("lose");
}
  1. 集合型Nim游戏,m个整数组合成的集合ai,给定n堆石子的数量bi,每次操作可以从任意一堆中拿去ai个石子最后无法进行操作的时候就视为失败
    思路:每一堆石子都是孤立的,把n堆石子看作n个有向图游戏
int sg(int x) {
    if (f[x] != -1) return f[x];
    unordered_set<int> s;
    for (int i = 0; i < m; i++) {
        if (x >= a[i]) s.emplace(sg(x - a[i]));
    }
    for (int i = 0; ; i++) {
        if (!s.count(i)) return f[x] = i;
    }
}

注:普通Nim游戏也可以转化为有向图游戏,可以证明SG(X) = X

  1. 剪纸游戏对于一个n * m的矩形网格纸,先剪出1 * 1格纸的玩家获胜
    边界:2 * 2, 2 * 3, 3 * 2 剪切后下一步一定能切有 1 * 1,因而其为必败态
    整张纸看作根结点,子图之间不独立,有子图的SG的值的异或作为根节点值
int n, m, f[N][N]; 
int sg(int a, int b) {
    if (f[a][b] != -1) return f[a][b];
    unordered_set<int> uset;
    for (int i = 2; i <= a - 2; i++) {
        uset.emplace(sg(i, b) ^ sg(a - i, b));
    }
    for (int i = 2; i <= b - 2; i++) {
        uset.emplace(sg(a, i) ^ sg(a, b - i));
    }
    for (int i = 0; ; i++) {
        if (!s.count(i)) return f[a][b] = f[b][a] = i;
    }
}

卡特兰数

C a t a l a n Catalan Catalan的基本模型为,从(0, 0)走到(n, n)只能向右或者向上且不能越过对角线的方案数

一般而言,当一种操作数不能超过另一种,或者两种操作不能有交集,这样的操作总数通常为卡特兰数。例如:

  1. 由n个0 和 n个1组成的字符串, 且所有前缀子串中一种字符的个数均大于另一种
  2. n组括号的合法式总数
  3. 出栈序列总数
  4. 不同的二叉树
  5. 圆上有 2 n 2n 2n个点, 把这些点成对连接, 但是互不相交
  6. n + 2边的凸多边形分成n个三角形的方法数

卡特兰数的通项公式1, H n = C 2 n n − C 2 n n − 1 H_n = C_{2n}^n - C_{2n}^{n - 1} Hn=C2nnC2nn1

解释: 路径总数为2n次中选择了n次向右移动,但其中有部分路径为非法的,这部分路径至少会与

y = x + 1 y = x + 1 y=x+1这条直线有一个交点,把交点后的线对称过去终点便会变为(n - 1, n + 1)

卡特兰的通项公式2, H n = 1 2 n + 1 C 2 n n H_n = \frac {1}{2n + 1}{C_{2n}^n} Hn=2n+11C2nn

解释: 由1简单展开得来

卡特兰数的递推公式(一般使用该公式) : H n = 4 n − 2 n + 1 H n − 1 H_n = \frac{4n - 2}{n + 1} H_{n - 1} Hn=n+14n2Hn1

快速幂

模 P 意义下的快速幂 O ( l o g ( n ) ) 模P意义下的快速幂O(log(n)) P意义下的快速幂O(log(n))

ll quickpow(ll a, ll b, ll p) {
    ll ans = 1;
    while (b) {
        if (b & 1) {
            ans = ans * a % p;
        }
        b >>= 1;
        a = a * a % p;
    }
    return ans;
}

容斥原理

时间复杂度 O ( 2 n ) O(2^n) O(2n)

集合的并

集合的交集的交错和(奇数个为正偶数个为负)

例题: 给定一个整数n和m个不同的质数 p 1 , p 2 . . . p m p_1, p_2...p_m p1,p2...pm求1~n中能被这些质数其中的至少一个整除的数的个数

显然地有

  1. 交集的大小等于n除以质数的乘积
  2. 使用二进制位表示集合,那么只需要从1到(1 << n) - 1枚举集合即可得到每一个状态
int n, m, prim[N];
int cal() {
    int res = 0;
    for (int i = 1; i < 1 << m; i++) {
        int t = 1, sign = -1;
        for (int j = 0; j < m; j++) {
            if (i & 1 << j) {
                if ((ll)t * prim[j] > n) {
                    t = 0; break;
                }
                t *= prim[j];
                sign = -sign;
            }
        }
        if (t) res += n / t * sign;
    }
    return res;
}

集合的交

集合的交 = 集合的全集 - 集合的补集的并(De Morgan率)

例题:有四种硬币,面值为 c 1 , c 2 , c 3 , c 4 c_1, c_2, c_3, c_4 c1,c2,c3,c4。某人去商店买东西去了n次,对于每一次的购买,他带了 d i d_i di i i i种硬币,想购买价值为 s s s的物品,请问每次有多少种付款方法。

直接使用多重背包会是 O ( 4 s n ) O(4sn) O(4sn)的复杂度,但是如果使用容斥原理即可优化掉s这一维度

即求 ∑ i = 1 4 c i x i = s , x i ≤ d i \sum_{i = 1}^4 c_ix_i = s, x_i \le d_i i=14cixi=s,xidi的非负整数解的个数

直接求不受限制的全集 - 超出条件范围的集合的并

故先跑完全背包预处理出所有的无限制方案数f[i], U = f[s]后减去超额使用方案即可

假设只有j种硬币超额使用那么其方案数 f [ s − c i ( d j + 1 ) ] f[s - c_i(d_j + 1)] f[sci(dj+1)]这是因为在多重背包的预处理中

使用 d j + k d_j + k dj+k种的方案数的和已经累加到了这其中,而对于有多种硬币超限的情况,利用容斥原理既可以得到很好的处理

int c[4], d[4], n, s;
ll f[100100];
void pack() {
    f[0] = 1;
    for (int i = 0; i < 4; i++) 
        for (int j = c[i]; j < 100100; j++) 
            f[j] += f[j - c[i]];
}
ll calc(ll s) {
    ll res = 0;
    for (int i = 1; i < 1 << 4; i++) {
        ll t = 0, sign = -1;
        for (int j = 0; j < 4; j++) {
            if (i & 1 << j) {
                t += c[j] * (d[j] + 1);
                sign = sign;
            }
        }
        if (s >= t) res += f[s - t] * sign;
    }
    return f[s] - res;
}

组合数

帕斯卡三角求组合数阵

C n m = C n − 1 m + C n − 1 m − 1 C_n^m = C_{n - 1}^m + C_{n - 1}^{m - 1} Cnm=Cn1m+Cn1m1

O ( n 2 ) O(n^2) O(n2)

void getC() {
    for (int i = 0; i < N; i++) {
        for (int j = 0; j <= i; j++) {
            if (j == 0 || j == i) C[i][j] = 1;
            else C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % mod;
        }
    }
}

C n m = n ! ( n − m ) ! m ! C^m_n = \frac{n!}{(n - m) ! m!} Cnm=(nm)!m!n!

阶乘结合逆元求组合数

O ( n l o g m o d ) O(nlogmod) O(nlogmod)
f[i]表示i! g[i] 表示i!的逆元

void init() {
    f[0] = g[0] = 1;
    for (int i = 1; i < N; i++) {
        f[i] = f[i - 1] * i % mod;
        g[i] = g[i - 1] * qpow(i, mod - 2) % mod; 
    }
}
ll getC (ll n, ll m) {
    return f[n] * g[m] % mod * g[n - m] % mod;
}

卢卡斯定理求大组合数问题

C n m ≡ C n / p m / p C n   m o d   p m   m o d   p ( m o d p ) C_n^m \equiv C^{m / p}_{n/p} C^{m\ mod\ p} _{n\ mod\ p} \pmod p CnmCn/pm/pCn mod pm mod p(modp)

// 同样的,先得到f[i], g[i]两个数组用于快速求阶乘和其逆元
int lucas(ll n, ll m, ll p) {
    if (m == 0) return 1;
    return lucas(n / p, m / p, p) * getC(n % p, m % p, p) % p;
}

高精度不取模求组合数

  1. 用线性筛得到范围内素数表
  2. 枚举质数 p i p_i pi,并且计算 C n m C_n^m Cnm中的质数 p i p_i pi的个数
  3. 利用高精度计算C[i] * p
    tips: n ! n! n!中的质数p的个数: S = n p + n p 2 +   ⋅ ⋅ ⋅ S = \frac{n}{p} + \frac{n}{p^2}+\ ··· S=pn+p2n+ ⋅⋅⋅
int C[N], len = 1; c[0] = 1;
int get(int n, int p) {
    int s = 0;
    while (n) s += n / p, n /= p;
    return s;
}
int getCp(int n,int m, int p) {
    return get(n, p) - get(m, p) - get(n - m, p);
}
void mul(int C[], int p, int& len) {
    int t = 0; // carry
    for (int i = 0; i < len; i++) {
        t += C[i] * p;
        C[i] = t % 10;
        t /= 10;
    }
    while (t) {
        C[len++] = t % 10;
        t /= 10;
    }
}
for (int i = 0; i < cnt; i++) {
    int p = prim[i];
    int s = getCp(n, m, p);
    while (s--) mul(C, p, len);
}

高精度打表求组合数

C n m = c [ n ] [ m ] C_n^m = c[n][m] Cnm=c[n][m]

void add(int c[], int a[], int b[]) {
    for (int i = 0; i < N; i++) {
        c[i] += a[i] + b[i];
        c[i + 1] += c[i] / 10;
        c[i] %= 10;
    }
}
void getC(int n, int k) {
    for (int i = 0; i < n; i++) {
        for (int j = 0; j <= i; j++) {
            if (j == 0) c[i][j][0] = 1;
            else add(c[i][j], c[i - 1][j], c[i - 1][j - 1]);
        }
    }
}

复数

在 C++ 中,复数类型定义使用 complex<float>、complex<double> 和 complex<long double>。
由于面向对象的多态性,下面函数的名字都是唯一的,无需 f 或 l 的后缀。
一个复数对象拥有成员函数 real 和 imag,可以访问实部和虚部。
一个复数对象拥有非成员函数 real、imag、abs、arg,返回实部、虚部、模和辐角。
一个复数对象还拥有非成员函数:norm 为模的平方,conj 为共轭复数。
一个复数对象还拥有非成员函数 exp、log(底为 e 的对数主值)、log10(底为 10 的对数主值,C 中没有)、
pow、sqrt、sin、cos、tan,含义与 C 中的含义相同。
在 C++14 及以后的版本中,定义了 字面量运算符 std::literals::complex_literals::if, i, il。
例如输入 100if、100i 和 100il,
三者将分别返回 std::complex<float>{0.0f, 100.0f}、
std::complex<double>{0.0, 100.0} 
以及 std::complex<long double>{0.0l, 100.0l}。
这使得我们可以方便地书写形如 auto z = 4 + 3i 的复数声明。

整除分块

分块的性质

  1. 分块的块数不会超过 2 ∣ n ∣ 2|\sqrt n| 2∣n
  2. i i i所在块的右端点为 ⌊ n ⌊ n i ⌋ ⌋ \lfloor \frac{n}{\lfloor \frac{n}{i} \rfloor} \rfloor inn
    ∑ i = 1 n f ( i ) ⌊ n i ⌋ \sum_{i=1}^nf(i)\lfloor{\frac{n}{i}}\rfloor i=1nf(i)in

先预处理出 f ( i ) f(i) f(i)的前缀和 s ( i ) = ∑ f = 1 i f ( j ) s(i) = \sum_{f=1}^if(j) s(i)=f=1if(j)

for (int l = 1; l <= n; l = r + 1) {
    r = n / (n / l);
    res += (s(r) - s(l - 1)) * (n / l);
}

注意: 如果整除部分被除数和n不为同一个数的时候,需要用 if (k / l == 0) break; r = min(n, k / (k / l));以处理边界条件

部分二级结论

  1. 只有完全平方数,以及完全平方数*2的数的因子和为奇数。

数论

质数相关

线性筛

vector<int> isprime(n + 1, 1);
vector<int> prime;
isprime[0] = isprime[1] = 0;
for (int i = 2; i <= n; i++) {
    if (isprime[i]) {
        prime.emplace_back(i);
    }
    for (int j = 0; i * prime[j] <= n; j++) {
        isprime[i * prime[j]] = 0;
        if (i % prime[j] == 0) break;
    }
}

筛出质数同时求约数的个数

约数个数定理: n = ∏ i = 1 s p i a i n=\prod_{i=1}^s p_i^{ai} n=i=1spiai, 那么有 d ( n ) = ∏ i = 1 s ( a i + 1 ) d(n)=\prod_{i=1}^s(a_i + 1) d(n)=i=1s(ai+1)

const int N = 1e6 + 10;
int p[N], vis[N], cnt;
int a[N], d[N]; // a记录最小质因子的个数
void get_d(int n) {
    d[1] = 1;
    for (int i = 2; i <= n; i++) {
        if (!vis[i]) {
            p[cnt++] = i;
            a[i] = 1; d[i] = 2;
        }
    }
    for (int j = 0; i * p[j] <= n; j++) {
        int m = i * p[j];
        vis[m] = 1;
        if (i % p[j] == 0) {
            a[m] = a[i] + 1;
            d[m] = d[i] / (a[i] + 1) * (a[m] + 1);
            break;
        }
        else {
            a[m] = 1; d[m] = d[i] * 2;
        }
    }
}

筛出的同时求约数的和

f ( n ) = ∏ i = 1 s ∑ j = 0 a i p i j f(n) = \prod_{i=1}^s\sum_{j=0}^{a_i}p_i^j f(n)=i=1sj=0aipij

const int N = 1000010;
int p[N], vis[N], cnt;
int g[N], f[N];
// g[i] = 1 + p ^ 1... + p ^ k 其中p为最小质因子
void get_f(int n) {
    g[1] = f[1] = 1;
    for (int i = 2; i <= n; i++) {
        if (!vis[i]) {
            p[cnt++] = i;
            g[i] = f[i] = i + 1;
        }
    }
    for (int j = 1; i * p[j] <= n; j++) {
        int m = i * p[j];
        vis[m] = 1;
        if (i % p[j] == 0) {
            g[m] = g[i] * p[j] + 1;
            f[m] = f[i] / g[i] * g[m];
            break;
        }
        else {
            g[m] = g[j] + 1;
            f[m] = f[i] * g[m];
        }
    }
}

筛法求出莫比乌斯函数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z9kzcN49-1691158557703)(image/数学/1690015543714.png)]
平方因子也就是相同质因子

const int N = 1e6 + 10
int p[N], vis[N], cnt;
int mu[N];
void get_mu(int n) {
    mu[1] = 1;
    for (int i = 2; i <= n; i++) {
        if (!vis[i]) {
            p[cnt++] = i;
            mu[i] = -1;
        }
        for (int j = 0; i * p[j] <= n; j++) {
            int m = i * p[j];
            vis[m] = 1;
            if (i % p[j] == 0) {
                mu[m] = 0;
                break;
            }
            else mu[m] = -mu[i];
        } 
    }
}

欧拉函数以及相关

筛法求欧拉函数

const int N = 1e6 + 10
int p[N], vis[N], cnt = 0;
int phi[N];
// p 存储质数
// vis 为当前数是否被筛出过
void get_phi(int n) {
    phi[1] = 1;
    for (int i = 2; i <= n; i++) {
        if (!vis[i]) {
            p[cnt++] = i;
            phi[i] = i - 1;
        }
        for (int j = 0; i * p[j] <= n; j++) {
            int m = i * p[j];
            vis[m] = 1;
            if (i % p[j] == 0) {
                phi[m] = p[j] * phi[i];
                break;
            }
            else 
                phi[m] = (p[j] - 1) * phi[i];
        }
    }
}

试除法求欧拉函数

int phi(int n) {
    int res = n;
    for (int i = 2; i * i <= n; i++) {
        if (n % i == 0) {
            res = res / i * (i - 1);
            while (n % i == 0) n /= i;
        }
    }
    if (n > 1) res = res / n * (n - 1);
    return res;
}

对于i,j两个质数,i,j中的所有数即(i < x < j) ,满足 p h i [ x ] < = p h i [ i ] , p h i [ x ] < p h i [ j ] phi[x] <= phi[i] , phi[x] < phi[j] phi[x]<=phi[i],phi[x]<phi[j]

欧拉定理

g c d ( a , m ) = 1 gcd(a, m) = 1 gcd(a,m)=1 a φ ( m ) ≡ 1 ( m o d m ) a^{\varphi(m)} \equiv 1 \pmod m aφ(m)1(modm) 当m为一质数的时候,由 φ ( m ) = m − 1 \varphi(m) = m - 1 φ(m)=m1即可得到费马小定理,可见,费马小定理实际上是欧拉定理中,满足 g c d ( a , m ) = 1 gcd(a, m) = 1 gcd(a,m)=1的情况下m为质数的特殊形式

扩展欧拉定理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EwHd0J7S-1691158557704)(image/数学/1690034886182.png)]

对于这一公式,实际上做大数字模运算的时候,当不考虑a,m是否相互互质的时候,直接考虑后两式算出结果即可,即当 b ≥ φ ( m ) b \ge \varphi(m) bφ(m)的时候运用公式降幂,否则不降幂。后调用快速幂计算出结果即可

int depow(int phi, string& s) {
    int b = 0;
    bool flag = false;
    for (auto& c : s) {
        b = b * 10 + c - '0';
        if (b >= phi) flag = true, b %= phi;
    }
    if (flag) b += phi;
    return b;
}

同余相关

费马小定理和快速幂求乘法逆元

乘法逆元: g c d ( a , b ) = 1 gcd(a, b) = 1 gcd(a,b)=1 a x ≡ 1 ( m o d b ) ax \equiv 1 \pmod b ax1(modb)则称 x x x为a模b的乘法逆元,记作 a − 1 a^{-1} a1

费马小定理: 若p为质数,且 g c d ( α , m ) = 1 , 那么有 gcd(\alpha, m) = 1,那么有 gcd(α,m)=1,那么有 α p − 1 ≡ 1 ( m o d p ) \alpha^{p-1} \equiv 1 \pmod p αp11(modp)
α ∗ α p − 2 ≡ 1 ( m o d p ) \alpha * \alpha^{p - 2} \equiv 1 \pmod p ααp21(modp)

因而可以用快速幂(mod p意义下)求乘法逆元 x = quickpow(a, p - 2, p)

注: 快速幂求乘法逆元首先需要 α \alpha α和p互质,若 α \alpha α作为变量,p为一质数,先判断a % p的情况

线性递推乘法逆元

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JvFlvB2h-1691158557704)(image/数学/1690114334583.png)]

inv[1] = 1;
for(int i = 2; i < p; ++ i)
    inv[i] = (p - p / i) * inv[p % i] % p;

威尔逊定理

p p p为质数为 ( p − 1 ) ! ≡ − 1 ( m o d p ) (p - 1)! \equiv -1\pmod p (p1)!1(modp)的充分必要条件

并可以得到以下推论,即对于任何大于等于2的正整数p:

  1. 若p为质数, 则 ( p − 1 ) ! + 1 ≡ 0 ( m o d p ) (p - 1)! + 1 \equiv 0\pmod p (p1)!+10(modp)
  2. 若p为大于4的合数,则 ( p − 1 ) ! ≡ 0 ( m o d p ) (p - 1)! \equiv 0 \pmod p (p1)!0(modp)

裴蜀定理

一定存在 x 1 , x 2 . . . . x i x_1, x_2....x_i x1,x2....xi,满足 ∑ i = 1 n A i x i = g c d ( A 1 . . . . A n ) \sum_{i = 1}^nA_ix_i = gcd(A_1....A_n) i=1nAixi=gcd(A1....An)

扩展欧几里得算法

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

从而构造通解:

x = x 0 + b g c d ( a , b ) ∗ k , y = y 0 − a g c d ( a , b ) ∗ k x = x_0 + \frac{b}{gcd(a, b)} * k, y = y_0 - \frac{a}{gcd(a, b)} * k x=x0+gcd(a,b)bk,y=y0gcd(a,b)ak

扩欧求解同余方程以及乘法逆元

对于同余方程 a x ≡ b ( m o d m ) ax \equiv b \pmod m axb(modm),可以转化为 a x + m y = b ax + my = b ax+my=b,用拓欧求解即可

求解乘法逆元时,即 a , m a, m a,m互质 b b b为1的情况,用扩展欧几里得算法先求出 a x + m y = g c d ( a , m ) ax + my = gcd(a, m) ax+my=gcd(a,m)

的解 x x x,最小正整数解即为 ( x % m + m ) % m (x \% m + m) \% m (x%m+m)%m

中国剩余定理

C h i n a   R e m a i n d e r   T h e o r e m China\ Remainder\ Theorem China Remainder Theorem

求解 x ≡ r i ( m o d m i ) x \equiv r_i \pmod {m_i} xri(modmi)形式的线性同余方程组,其中 m i m_i mi两两互质,求 x x x的最小非负整数解

  1. 计算所有模数的乘积M
  2. 计算第 i i i个方程的 c i = M m i c_i = \frac{M}{m_i} ci=miM
  3. 计算 c i c_i ci在模 m i m_i mi的意义下的逆元
  4. $ x = \sum_{i=1}mr_ic_ic_i{-1}\pmod M$
ll crt(ll m[], ll r[]) {
    ll M = 1, ans = 0;
    for (int i = 0; i < n; i++) M *= m[i];
    for (int i = 0; i < n; i++) {
        ll c = M / m[i], c_, y;
        exgcd(c, m[i], c_, y);
        ans = (ans + r[i] * c * c_ % M) % M;
    }
    return (ans % M + M) % M;
}

EXCRT

x ≡ r 1 ( m o d m 1 ) , x ≡ r 2 ( m o d m 2 ) x \equiv r_1 \pmod {m_1}, x \equiv r_2 \pmod {m_2} xr1(modm1),xr2(modm2)

两个方程可以合并为

m 1 p − m 2 q = r 2 − r 1 m_1p - m_2q = r_2 - r_1 m1pm2q=r2r1, 其通解 P = p + m 2 / d ∗ k P = p + m_2 / d * k P=p+m2/dk

x ≡ r ( m o d m ) x \equiv r \pmod m xr(modm)

其中$ r = m_1p +r_1, m = lcm(m_1, m_2)$ 不断地合并这些式子,就能够求解线性同余方程组

// m为模数数组,r为余数数组
ll excrt(ll m[], ll r[]) {
    ll m1, m2, r1, r2, p, q;
    m1 = m[1], r1 = r[1];
    for (int i = 2; i <= n; i++) {
        m2 = m[i], r2 = r[i];
        ll d = exgcd(m1, m2, p, q);
        if ((r2 - r1) % d) return -1;
        p = p * (r2 - r1) / d;
        // 构造一个正数的解
        p = (p % (m2 / d) + m2 / d) % (m2 / d);
        r1 = m1 * p + r1;
        m1 = m1 * m2 / d;
    }
    return (r1 % m1 + m1) % m1;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

魈宝不想写程序

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值