算法基础-数学知识
- ≡ \equiv ≡:同余符号
- 该文档所有用到的题目来自 ACWING,本文档作者 肥羊也 (acwing: 这个网站哟西嘚斯,优快云:肥羊也),文档仅供学习交流,禁止商用。
- 鸣谢:感谢 y y y 总讲解,从 y y y 总身上学到很多知识,喝水不忘挖井人,在这里向他以及 ACWING 上提供讲解的小伙伴,表示诚挚的感谢!
- 重要知识目录:
- 分解质因数
- 筛法求质数
- g c d gcd gcd
- 欧拉函数
- 快速幂
- 扩展 g c d gcd gcd
- 逆元
- 高斯消元
- 组合数
- DP法
- 预处理阶乘和逆元法
- 卢卡斯定理
- 容斥原理
- 博弈论
- NIM 游戏
- SG 函数
质数
判断质数
- 从定义出发:对于一个数字 n,除了 1 和 本身 没有其他的因数。即
2 ~ n-1
没有任何一个数字可以整除 n。 - 试除法: 可以发现一个数的因数都是成对出现的,所以其实不用判断范围
2 ~ n-1
,只需要判断 2 → n 2 \rightarrow \sqrt{n} 2→n 即可。(代码有个小优化,我们判断是否到 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;
}
分解质因数
- 朴素做法:先找到
2 ~ n
的所有质数,然后试除法,如果n % x == 0
说明该数是一个质因数,就可以求得其指数。 - 试除法改进:从 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 - n 中的数字,把不是质数的筛去,最后只剩下质数。
- 朴素筛法:每次循环到当前数,就把当前数的所有小于 n 的倍数都筛去。
- 埃氏筛法:每个数都至少有一个质因数,因此合数其实是没有必要进行筛的操作,只需要当前数如果是质数,就把当前数小于 n 的倍数都筛去。
- 线性筛法:每个数都让他被他的最小质因数筛去,保证每个数都只被筛一次。
#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 - n
- 从 1 − n 1 - \sqrt{n} 1−n,约数是成对存在的
#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 。
- 分解质因数
- 次数相乘求解
#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;
}
最大公约数
- 求两个数分别的因数,然后找相同的最大的
- 辗转相除法
#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 ~ 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×(1−p11)×(1−p21)×(1−p31)×...×(1−pk1) 。(优化技巧,这里防止整数溢出做同样的处理:
res = res / a * (a - 1)
) - 筛法求欧拉函数
// 定义求欧拉函数
#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) (n−1) 所以有 a n − 1 m o d n = 1 a^{n - 1} \ mod \ {n} = 1 an−1 mod n=1 (费马定理)
快速幂
- 快速幂
- 快速幂求逆元
- 对于 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) ba≡a×x(modm),则称 x 为 b 的模 m 的乘法逆元。b 存在乘法逆元的充分必要条件是,b 与 m 互质。当 m 为 质数的时候, b m − 2 b ^ {m - 2} bm−2 即为逆元(费马定理)。
// 快速幂
#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
a、b 的
x
、
y
x、y
x、y 和
b
、
a
m
o
d
b
b、 a\;\;mod\;\;b
b、amodb 的
x
′
、
y
′
x'、y'
x′、y′ 的关系,因为
a
、
b
a、b
a、b 和
b
、
a
m
o
d
b
b、 a\;\;mod\;\;b
b、amodb 的最大公约数相同,可以看作求
b
、
a
m
o
d
b
b、 a\;\;mod\;\;b
b、amodb 的
x
′
、
y
′
x'、y'
x′、y′,再转化为
a
、
b
a、b
a、b 的
x
、
y
x、y
x、y。
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=a−⌊ba⌋×b则有:ax+b(y−⌊ba⌋×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=x0−db×ky=y0+da×k(gcd(a,b)=dandk∈Z)
所有解的形式的证明:代入
a
x
+
b
y
=
d
ax + by = d
ax+by=d
线性同余方程
求解 a x ≡ b ( m o d m ) ax \equiv b \quad( mod \; m) ax≡b(modm) 的 x:
相当于 a x = m y + b ax = my + b ax=my+b 即 a x − m y = b ax - my = b ax−my=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(k∈Z)
对于得到的任意答案 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\; 互质) ax≡ba(modp,且p非质数,b和p互质)
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 x、y 的系数都是减小的趋势。这里我感觉很中二啊,留一个标记,不知道有没有更好的解法。)
中国剩余定理
中国剩余定理要求任意两个 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×M1−1+a2×M2×M2−1+...+ak×Mk×Mk−1(M−1为M的逆元)
对于任意两个 m 不能保证互质的,可以两两求解,最后一次求得的即为所有的解。首先根据前面的线性同余方程有如下结论:
a
x
+
m
y
=
b
等
价
于
:
a
x
≡
b
(
m
o
d
m
)
ax + my = b \\ 等价于:\\ ax \equiv b \quad(mod \; m)
ax+my=b等价于:ax≡b(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
x≡a1(modm1)x≡a2(modm2)x≡a3(modm4)...x≡ak(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
x≡a1(modm1)x≡a2(modm2)可转化为:x=k1×m1+a1x=k2×m2+a2则有:k1×m1−k2×m2=a2−a1
而上式可以用扩展欧几里得求出
k
1
、
k
2
k_1 、k_2
k1、k2 ,其通项为:
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而且需要满足前提条件:(a2−a1)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′+a′则x的最小正整数解为:(a′modm′+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;
}
高斯消元
高斯消元解线性方程组
- 把当前列 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。
- 用 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,重复这两个步骤直到结束。
- 如果 r < n r < n r<n 可能有两种情况无穷多解或者无解,只要增广矩阵出现 0 ! = 0 0 != 0 0!=0 的情况就是无解。
- 否则有解,从下往上,把每一行除了第一个数之外其他的数都减为 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),枚举下一列、下一行
- 如果 r < n,看看从 r 开始后面有没有 0 != 0 的情况,有就是无解,否则无穷多组解
- 否则倒着减去相应的值,求解答案,最后输出
#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=Ci−1j+Ci−1j−1
对于 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=a∗x(modp) 称 x x x 为 y y y 的 m o d p mod\;\; p modp 的逆元。当 p p p 是质数, y p − 2 y^{p-2} yp−2 就是 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+x)p≡(1+xp)(modp)(1+x)pa≡(1+xpa)(modp)
证明1
首先我们可以把 a 、 b a、b a、b 转化为 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=ak⋅pk+ak−1⋅pk−1+...+a1⋅p1+a0⋅p0
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=bk⋅pk+bk−1⋅pk−1+...+b1⋅p1+b0⋅p0
则有: ( 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)ak⋅pk+ak−1⋅pk−1+...+a1⋅p1+a0⋅p0
= ( 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)ak⋅pk⋅(1+x)ak−1⋅pk−1⋅...⋅(1+x)a1⋅p1⋅(1+x)a0⋅p0
≡ ( 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+xpk−1)ak−1⋅...⋅(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=xbk⋅pk+bk−1⋅pk−1+...+b1⋅p1+b0⋅p0
显然, x b k ⋅ p k x^{b_k\cdot p^k} xbk⋅pk 项在 ( 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} Cakbk⋅xbk⋅pk ,同理 x b k − 1 ⋅ p k − 1 x^{b_{k-1}\cdot p^{k-1}} xbk−1⋅pk−1 项在 ( 1 + x p k − 1 ) a k − 1 (1 + x^{p^{k-1}})^{a_{k-1}} (1+xpk−1)ak−1 中是 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}} Cak−1bk−1⋅xbk−1⋅pk−1,同理可求得 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}} Cakbk⋅Cak−1bk−1⋅...⋅Ca1b1⋅Ca0b0
因此可得: 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) Cab≡Cakbk⋅Cak−1bk−1⋅...⋅Ca1b1⋅Ca0b0(modp)
因为 a 、 b a、b a、b 是 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 a、b 在 p p p 进制中右移一位,即 ⌊ a p ⌋ 、 ⌊ b p ⌋ \lfloor \frac{a}{p} \rfloor、\lfloor \frac{b}{p} \rfloor ⌊pa⌋、⌊pb⌋
对于 ⌊ a p ⌋ 、 ⌊ b p ⌋ \lfloor \frac{a}{p} \rfloor、\lfloor \frac{b}{p} \rfloor ⌊pa⌋、⌊pb⌋ 重复上面最开始的步骤,同样可以得到: 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) C⌊pa⌋⌊pb⌋≡Cakbk⋅Cak−1bk−1⋅...⋅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) Cab≡C⌊pa⌋⌊pb⌋⋅Camodpbmodp(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⌋=qa⌊pb⌋=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=0∑aCak⋅xk且:(1+x)a=(1+x)qa⋅p+ra=(1+x)qa⋅p⋅(1+x)ra≡(1+xp)qa⋅(1+x)ra(modp)(因为(1+x)p≡(1+xp)(modp))=i=0∑qaCqa i⋅xi⋅p⋅j=0∑rACra j⋅xj=i=0∑qaj=0∑rACqa i⋅Cra j⋅xi⋅p+j
令
k
=
i
⋅
p
+
j
k = i \cdot p + j
k=i⋅p+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=0∑aCqa⌊pk⌋⋅Cra kmodp⋅xk
综上有:
(
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)a≡k=0∑aCqa⌊pk⌋⋅Cra kmodp⋅xk(modp)且:(1+x)a=k=0∑aCak⋅xk故:k=0∑aCak⋅xk≡k=0∑aCqa⌊pk⌋⋅Cra kmodp⋅xk(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) \\ 得证
Cab≡Cqa⌊pb⌋⋅Cra bmodp(modp)即:Cab≡C⌊pa⌋⌊pb⌋⋅C 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) (n−1,n+1)。即每一条不合法路径都可以转化成到 ( n − 1 , n + 1 ) (n - 1, n + 1) (n−1,n+1) 的一条路径(同理,因为 原点到 ( n − 1 , n + 1 ) (n - 1, n + 1) (n−1,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} C2⋅nn−C2⋅nn−1
可化简为 C 2 ∗ n n n + 1 \frac{C_{2*n}^{n}}{n + 1} n+1C2∗nn
#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 2n−1 (所以有, ∑ i = 1 m C m i = 2 n − 1 \sum_{i = 1}^{m}C_{m}^{i} = 2^n - 1 ∑i=1mCmi=2n−1)。对于每一种组合如果他包含奇数个单个集合,我们就应该加上这个这种组合的方案数,否则减去。
每一项如何计算呢? 我们可以用位运算枚举每一种组合,记录一下每一种组合的值 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−(ai⨁x) 个,则该堆变为 a i ⨁ x a_i \bigoplus x ai⨁x 个,所有堆的异或和就会变为 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;
}