杂项
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个有向图游戏
- 有向图无环上的棋子游戏
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");
}
- 集合型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
- 剪纸游戏对于一个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)只能向右或者向上且不能越过对角线的方案数
一般而言,当一种操作数不能超过另一种,或者两种操作不能有交集,这样的操作总数通常为卡特兰数。例如:
- 由n个0 和 n个1组成的字符串, 且所有前缀子串中一种字符的个数均大于另一种
- n组括号的合法式总数
- 出栈序列总数
- 不同的二叉树
- 圆上有 2 n 2n 2n个点, 把这些点成对连接, 但是互不相交
- 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=C2nn−C2nn−1
解释: 路径总数为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+14n−2Hn−1
快速幂
模 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中能被这些质数其中的至少一个整除的数的个数
显然地有
- 交集的大小等于n除以质数的乘积
- 使用二进制位表示集合,那么只需要从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,xi≤di的非负整数解的个数
直接求不受限制的全集 - 超出条件范围的集合的并
故先跑完全背包预处理出所有的无限制方案数f[i], U = f[s]后减去超额使用方案即可
假设只有j种硬币超额使用那么其方案数 f [ s − c i ( d j + 1 ) ] f[s - c_i(d_j + 1)] f[s−ci(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=Cn−1m+Cn−1m−1
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=(n−m)!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 Cnm≡Cn/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;
}
高精度不取模求组合数
- 用线性筛得到范围内素数表
- 枚举质数 p i p_i pi,并且计算 C n m C_n^m Cnm中的质数 p i p_i pi的个数
- 利用高精度计算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 的复数声明。
整除分块
分块的性质
- 分块的块数不会超过 2 ∣ n ∣ 2|\sqrt n| 2∣n∣
-
i
i
i所在块的右端点为
⌊
n
⌊
n
i
⌋
⌋
\lfloor \frac{n}{\lfloor \frac{n}{i} \rfloor} \rfloor
⌊⌊in⌋n⌋
求 ∑ 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));
以处理边界条件
部分二级结论
- 只有完全平方数,以及完全平方数*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=1s∑j=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)=m−1即可得到费马小定理,可见,费马小定理实际上是欧拉定理中,满足 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 ax≡1(modb)则称 x x x为a模b的乘法逆元,记作 a − 1 a^{-1} a−1
费马小定理: 若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
αp−1≡1(modp)
有
α
∗
α
p
−
2
≡
1
(
m
o
d
p
)
\alpha * \alpha^{p - 2} \equiv 1 \pmod p
α∗αp−2≡1(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 (p−1)!≡−1(modp)的充分必要条件
并可以得到以下推论,即对于任何大于等于2的正整数p:
- 若p为质数, 则 ( p − 1 ) ! + 1 ≡ 0 ( m o d p ) (p - 1)! + 1 \equiv 0\pmod p (p−1)!+1≡0(modp)
- 若p为大于4的合数,则 ( p − 1 ) ! ≡ 0 ( m o d p ) (p - 1)! \equiv 0 \pmod p (p−1)!≡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)b∗k,y=y0−gcd(a,b)a∗k
扩欧求解同余方程以及乘法逆元
对于同余方程 a x ≡ b ( m o d m ) ax \equiv b \pmod m ax≡b(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} x≡ri(modmi)形式的线性同余方程组,其中 m i m_i mi两两互质,求 x x x的最小非负整数解
- 计算所有模数的乘积M
- 计算第 i i i个方程的 c i = M m i c_i = \frac{M}{m_i} ci=miM
- 计算 c i c_i ci在模 m i m_i mi的意义下的逆元
- $ 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} x≡r1(modm1),x≡r2(modm2)
两个方程可以合并为
m 1 p − m 2 q = r 2 − r 1 m_1p - m_2q = r_2 - r_1 m1p−m2q=r2−r1, 其通解 P = p + m 2 / d ∗ k P = p + m_2 / d * k P=p+m2/d∗k
x ≡ r ( m o d m ) x \equiv r \pmod m x≡r(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;
}