【数学】
算法模板
算法应用
【约数】
算法模板
试除法分解质因数
void divide(int x)
{
for (int i = 2; i <= x / i; i ++ )
if (x % i == 0)
{
int s = 0; // i用来表示底数,s用来吧表示指数
while (x % i == 0)
{
x /= i;
s ++ ;
}
cout << i << ' ' << s << endl;
}
if (x > 1) cout << x << ' ' << 1 << endl;
cout << endl;
}
试除法求所有约数
vector<int> 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());
return res;
}
约数个数和约数之和
unordered_map<int, int> primes;
while (n--)
{
int x; cin >> x;
for (int i=2; i<=x/i; i++)
if (x%i==0)
{
while (x%i==0)
{
x/=i;
primes[i]++;
}
}
if (x>1) primes[x]++;
}
// 如果 N = p1^c1 * p2^c2 * ... *pk^ck
// 约数个数:
// (c1 + 1) * (c2 + 1) * ... * (ck + 1)
for (auto prime : primes)
res = res * (prime.second + 1) % mod;
cout << res << endl;
// 约数之和:
// (p1^0 + p1^1 + ... + p1^c1) * ... * (pk^0 + pk^1 + ... + pk^ck)
for (auto prime : primes)
{
int p = prime.first, a = prime.second;
int t = 1;
while (a--) t = (t * p + 1) % mod;
res = res * t % mod;
}
算法应用
| 题目来源 |
标题 |
难度 |
星级 |
考察算法 |
一句话思路总结 |
| AcWing |
867 分解质因数 |
简单 |
⭐ |
试除法 |
对每个数 \(x\),从 \(2\) 枚举到 \(\lfloor\sqrt{x}\rfloor\)(用 i <= x / i 防溢出),若 \(i\) 整除 \(x\) 则循环除尽并统计指数输出,枚举结束后若 \(x > 1\) 则 \(x\) 本身为最后一个质因数,时间复杂度 \(O(n\sqrt{a_i})\)。 |
| AcWing |
869 试除法求约数 |
简单 |
⭐⭐ |
试除法 |
利用约数成对定理,枚举 \(i\) 从 \(1\) 到 \(\lfloor\sqrt{x}\rfloor\)(用 i <= x / i 防溢出),若 \(x \bmod i = 0\) 则 \(i\) 和 \(x/i\) 均为约数(注意平方数去重),最后排序输出,时间复杂度 \(O(n\sqrt{a_i})\)。 |
| AcWing |
870 约数个数 |
简单 |
⭐⭐⭐ |
质因数分解 + 约数个数公式 |
对每个 \(a_i\) 试除法分解质因数,用哈希表累加各质因数的总指数,最后根据约数个数公式 \(\prod (c_i + 1)\) 计算结果并对 \(10^9+7\) 取模,时间复杂度 \(O(n\sqrt{A})\)。 |
| AcWing |
871 约数之和 |
简单 |
⭐⭐⭐ |
质因数分解 + 约数之和公式 |
对每个 \(a_i\) 试除法分解质因数,用哈希表累加各质因数的总指数 \(c_i\),然后对每个质因数利用递推式 \(t = t \times p + 1\) 计算等比数列和 \(1 + p + p^2 + \cdots + p^{c_i}\),最后将所有等比数列和相乘并对 \(10^9+7\) 取模,时间复杂度 \(O(n\sqrt{A} + C)\)。 |
| 洛谷 |
[[B3871 因数分解]] |
普及- |
|
|
|
| 洛谷 |
[[B4077 鼓掌]] |
普及- |
|
|
|
| 洛谷 |
P1029 最大公约数和最小公倍数问题 |
普及- |
|
|
|
| 洛谷 |
[[P1458 顺序的分数]] |
普及- |
|
|
|
| 洛谷 |
[[P2043 质因子分解]] |
普及- |
|
|
|
| 洛谷 |
P2118 比例简化 |
普及- |
|
|
|
| 洛谷 |
[[P10720 小杨的幸运数字]] |
普及- |
|
|
|
| 洛谷 |
P13014 最大公因数 |
普及- |
⭐⭐⭐ |
整数二分 + 贪心调整 |
设兑换 \(mid\) 份奖品,每份需 \(a\) 张券1和 \(b\) 张券2(或互换),二分答案后用贪心调整:若券1不足则将部分"券1为主"的方案换成"券2为主"(每次调整节省 \(a-b\) 张券1、多花 \(a-b\) 张券2),检查调整后两种券均不超限制,时间复杂度 \(O(\log \min(n,m))\),空间复杂度 \(O(1)\) |
| 洛谷 |
P15798 有限不循环小数 |
普及- |
⭐⭐⭐ |
数论 / 质因子分解 |
利用定理:\(\frac{1}{a}\) 为有限小数当且仅当 \(a\) 的质因子仅含 \(2\) 和 \(5\),遍历 \([L,R]\) 用 while 除尽 \(2\) 和 \(5\) 后判断是否剩 \(1\),时间复杂度 \(O((R-L+1) \cdot (\log_2 a + \log_5 a))\),空间复杂度 \(O(1)\) |
| 洛谷 |
B4070 奇妙数字 |
普及 |
|
|
|
| 洛谷 |
[[P10263 公倍数问题]] |
普及 |
|
|
|
| 洛谷 |
P1072 Hankson的趣味题 |
普及+ |
|
|
|
| 洛谷 |
P1447 能量采集 |
提高+ |
|
|
|
| AtCoder |
AT_awc0030_e Optimizing Team Division |
|
|
|
|
| AtCoder |
AT_awc0045_d Cell Division |
|
|
|
|
| 学而思编程 |
第K个约数 |
语言基础 |
⭐⭐ |
约数枚举 |
先求所有数的和 \(m\),再枚举到 \(\sqrt{m}\) 找出所有约数,排序后取第 \(k\) 个。 |
| 学而思编程 |
区间外最大公约数 |
普及奠基 |
⭐⭐⭐ |
前缀GCD + 后缀GCD |
预处理前缀 \(\gcd\) 数组 \(\text{lgcd}[i]\) 和后缀 \(\gcd\) 数组 \(\text{rgcd}[i]\),查询时 \(\gcd(\text{lgcd}[l-1], \text{rgcd}[r+1])\)。 |
| 学而思编程 |
完全平方数 |
语言基础 |
⭐⭐ |
质因数分解 |
将 \(n\) 分解质因数,每个质因数的指数为奇数时补乘一次,使所有指数变为偶数。 |
【质数】
算法模板
试除法判定质数
bool is_prime(int x)
{
if (x < 2) return false;
for (int i = 2; i <= x / i; i ++ )
if (x % i == 0)
return false;
return true;
}
埃式筛法求素数
模板
int primes[N], cnt; // primes[]存储所有素数
bool st[N]; // st[x]存储x是否被筛掉
void get_primes(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;
}
}
线性筛法求素数
模板
int primes[N], cnt; // primes[]存储所有素数
bool st[N]; // st[x]存储x是否被筛掉
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;
}
}
}
//线性筛求所有数的质因子个数
for (int i=2; i<=MA; i++)
{
if (!vis[i]) pri[++tot]=i, pn[i] = 1;
for (int j=1; j<=tot && i*pri[j]<=MA; j++)
{
vis[i*pri[j]] = true;
if (i%pri[j]!=0)
pn[i*pri[j]] = pn[i]+1;
else
{
pn[i*pri[j]] = pn[i];
break;
}
}
}
算法应用
| 题目来源 |
标题 |
难度 |
星级 |
考察算法 |
一句话思路总结 |
| AcWing |
866 试除法判定质数 |
简单 |
⭐ |
试除法 |
对每个数 \(x\),枚举 \(i\) 从 \(2\) 到 \(\lfloor\sqrt{x}\rfloor\)(用 i <= x / i 防溢出),若存在 \(i\) 整除 \(x\) 则 \(x\) 为合数,否则为质数,时间复杂度 \(O(n\sqrt{a_i})\)。 |
| AcWing |
868 筛质数 |
简单 |
⭐⭐ |
埃氏筛 / 线性筛 |
利用"质数的倍数必为合数"的性质,通过埃氏筛 \(O(n\log\log n)\) 或线性筛 \(O(n)\) 预处理 \([1,n]\) 内所有质数,输出质数个数;线性筛通过保证每个合数仅被其最小质因数筛去一次,将复杂度严格降至线性。 |
| AcWing |
1292 哥德巴赫猜想 |
|
|
|
|
| AcWing |
1293 夏洛克和他的女朋友 |
|
|
|
|
| AcWing |
5367 不合群数 |
|
|
|
|
| 洛谷 |
P1304 哥德巴赫猜想 |
入门 |
|
|
|
| 洛谷 |
B3939 绝对素数 |
普及- |
|
|
|
| 洛谷 |
[[B3969 B-smooth数]] |
普及- |
|
|
|
| 洛谷 |
P1075 质因数分解 |
普及- |
|
|
|
| 洛谷 |
P1125 笨小猴 |
普及- |
|
|
|
| 洛谷 |
P1217 回文质数 |
普及- |
|
|
|
| 洛谷 |
P3383 线性筛素数 |
普及- |
|
|
|
| 洛谷 |
P5723 质数口袋 |
普及- |
|
|
|
| 洛谷 |
P5736 质数筛 |
普及- |
|
|
|
| 洛谷 |
P14073 数字选取 |
普及- |
|
|
|
| 洛谷 |
AT_abc400_e Ringo's Favorite Numbers 3 |
普及 |
|
|
|
| 洛谷 |
[[B3929 小杨的幸运数]] |
普及 |
|
|
|
| 洛谷 |
[[B4050 挑战怪物]] |
普及 |
|
|
|
| 洛谷 |
P14918 相等序列 |
普及 |
|
|
|
| 洛谷 |
P1069 细胞分裂 |
普及+ |
|
|
|
| 学而思编程 |
回文质数(一) |
语言基础 |
⭐⭐ |
质数 / 回文数构造 |
枚举回文数的一半构造完整回文数,再判断是否为质数,避免直接枚举 \([L,R]\) 范围内所有数。 |
| 学而思编程 |
计算因质数 |
语言基础 |
⭐⭐ |
质数 / 筛法预处理 |
用类似筛法预处理每个数的所有质因数乘积,查询时 \(O(1)\) 输出。 |
| 学而思编程 |
区间质数 |
普及奠基 |
⭐⭐⭐ |
质数筛 + 前缀和 |
先用埃氏筛预处理 \(10^7\) 内质数,再用前缀和数组 \(s[i]\) 记录前 \(i\) 个数中质数个数,区间查询 \(O(1)\) 回答。 |
| 学而思编程 |
特殊素数对 |
语言基础 |
⭐⭐ |
质数判定 |
判断 \(n\) 是否为素数且存在距离为 \(6\) 的素数邻居;若不是则从 \(n+1\) 开始找第一个特殊素数。 |
| 学而思编程 |
幸运数字 |
普及奠基 |
⭐⭐⭐ |
质数筛 + 前缀和 |
埃氏筛预处理 \(10^7\) 内质数,再标记所有两质数乘积,最后前缀和统计区间 \([L,R]\) 内幸运数字个数。 |
| 学而思编程 |
质数 |
语言基础 |
⭐ |
质数判定 |
将区间端点调整为以 \(1\) 结尾的数,步长 \(10\) 枚举并判定素数。 |
【快速幂】
算法模板
求快速幂
// 求 m^k mod p,时间复杂度 O(logk)。
int qmi(int a, int b, int p)
{
int mul = 1;
while (b)
{
if (b&1) mul = mul * a % p;
a = a * a % p;
b >>= 1;
}
return mul;
}
// 如果是求快速幂
res = qmi(a, k, p);
// 如果是通过快速幂求逆元
res = qmi(a, p-2, p); // a^{p-2}就是a mod p的逆元
//预处理阶乘、逆元、逆元阶乘
// 预处理阶乘数组f
f[0] = f[1] = 1;
for (int i = 2; i <= n; i++)
{
f[i] = f[i - 1] * i;
f[i] %= mod;
}
// 预处理逆元数组inv
inv[1] = 1;
for (int i = 2; i <= n; i++)
{
inv[i] = (mod - (mod / i) * inv[mod % i]) % mod;
inv[i] = (inv[i] + mod) % mod; // 确保结果为正
}
// 预处理逆元阶乘数组g
g[0] = g[1] = 1;
for (int i = 2; i <= n; i++)
{
g[i] = g[i - 1] * inv[i];
g[i] %= mod;
}
求逆元
// 如果是通过快速幂求逆元
res = qmi(a, p-2, p); // a^{p-2}就是a mod p的逆元
//预处理阶乘、逆元、逆元阶乘
// 预处理阶乘数组f
f[0] = f[1] = 1;
for (int i = 2; i <= n; i++)
{
f[i] = f[i - 1] * i;
f[i] %= mod;
}
// 预处理逆元数组inv
inv[1] = 1;
for (int i = 2; i <= n; i++)
{
inv[i] = (mod - (mod / i) * inv[mod % i]) % mod;
inv[i] = (inv[i] + mod) % mod; // 确保结果为正
}
// 预处理逆元阶乘数组g
g[0] = g[1] = 1;
for (int i = 2; i <= n; i++)
{
g[i] = g[i - 1] * inv[i];
g[i] %= mod;
}
算法应用
| 题目来源 |
标题 |
难度 |
星级 |
考察算法 |
一句话思路总结 |
| AcWing |
875 快速幂 |
简单 |
⭐⭐ |
快速幂(二进制幂) |
将指数 \(b\) 按二进制分解,通过底数不断平方(\(a \to a^2 \to a^4 \to \cdots\))预计算各二进制位对应的幂次,若当前二进制位为 \(1\) 则乘入结果,每次乘法和平方后立即取模,时间复杂度 \(O(n \cdot \log b_i)\)。 |
| AcWing |
876 快速幂求逆元 |
简单 |
⭐⭐⭐ |
费马小定理 + 快速幂 |
由费马小定理得 \(a^{p-1} \equiv 1 \pmod{p}\),故 \(a\) 的逆元为 \(a^{p-2} \bmod p\);若 \(a \bmod p = 0\) 则逆元不存在输出 impossible,否则用快速幂 \(O(\log p)\) 计算,适用于模质数下的除法运算。 |
| AcWing |
5956 取余运算 |
|
|
|
|
| 洛谷 |
P1226 快速幂 |
普及- |
|
|
|
| 洛谷 |
AT_abc363_d Palindromic Number |
普及 |
|
|
|
| 洛谷 |
P3197 越狱 |
普及 |
|
|
|
| 洛谷 |
P15802 拆分 |
普及 |
⭐⭐⭐ |
数学贪心 + 快速幂 |
由数学结论:拆分出尽可能多的 \(3\) 可使乘积最大,\(n \bmod 3 = 0\) 时全拆 \(3\),余 \(1\) 时拆一个 \(4\)(\(2+2\)),余 \(2\) 时留一个 \(2\),用快速幂求 \(3^k \bmod 10^9\),时间复杂度 \(O(t \log n)\),空间复杂度 \(O(1)\) |
| AtCoder |
AT_awc0063_c Maximizing Investment |
|
|
|
|
【矩阵快速幂】
算法模板
struct matrix
{
int c[2][2];
matrix()
{
memset(c, 0, sizeof(c));
}
};
// 重载 *,表示矩阵加法后最大值
matrix operator*(matrix &x, matrix &y)
{
matrix t;
for (int i=0; i<2; i++)
for (int j=0; j<2; j++)
for (int k=0; k<2; k++)
{
t.c[i][j] = max(t.c[i][j], x.c[i][k]+y.c[k][j]);
}
return t;
}
// 重载 *,表示矩阵乘法
matrix operator*(matrix &x, matrix &y)
{
matrix t;
for (int i=0; i<2; i++)
for (int j=0; j<2; j++)
for (int k=0; k<2; k++)
{
t.c[i][j] = (t.c[i][j] + x.c[i][k]*y.c[k][j])%mod;
}
return t;
}
// 快速幂
matrix qmi(matrix a, int k)
{
matrix res;
for (int i=0; i<n; i++) // 需要乘法运算,所以需要为单位矩阵赋初始值
res.c[i][i] = 1;
while (k)
{
if (k&1) res = res * a;
a = a * a;
k >>= 1;
}
return res;
}
算法应用
| 题目来源 |
标题 |
难度 |
星级 |
考察算法 |
一句话思路总结 |
| AtCoder |
AT_awc0021_e Field Watering Plan |
|
⭐⭐⭐⭐⭐ |
Max-Plus 矩阵快速幂 |
将两天的状态(前一天是否选A)建模为2×2转移矩阵,在 max-plus 半环(加法→max、乘法→+)下进行矩阵快速幂,\(M^n\) 的最大元素即为 \(N\) 天的最大总生长量,时间复杂度 \(O(\log N)\)。 |
【欧拉定理】
算法模板
求欧拉函数
int phi(int x)
{
int res = x;
for (int i = 2; i * i <= x; i ++ )
if (x % i == 0)
{
res = res / i * (i - 1); // 等价于 res * (1 - 1/i)
while (x % i == 0) x /= i;
}
if (x > 1) res = res / x * (x - 1);
return res;
}
筛法求欧拉函数
int primes[N], cnt; // primes[]存储所有素数
int phi[N]; // 存储每个数的欧拉函数
bool st[N]; // st[x]存储x是否被筛掉
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; // 如果是质数,1~i-1 都与i互质
}
for (int j = 0; primes[j] <= n/i; j ++ )
{
st[primes[j] * i] = true;
if (i % primes[j] == 0)
{
phi[primes[j] * i] = phi[i] * primes[j];
break;
}
phi[primes[j] * i] = phi[i] * (primes[j] - 1);
}
}
}
算法应用
| 题目来源 |
标题 |
难度 |
星级 |
考察算法 |
一句话思路总结 |
| AcWing |
873 欧拉函数 |
简单 |
⭐⭐⭐ |
试除法质因数分解 + 欧拉函数公式 |
对每个 \(a_i\) 试除法分解质因数,每遇到一个质因数 \(p\) 就用公式 \(\varphi(n) = n \cdot \prod (1 - 1/p)\) 更新结果 res = res / p * (p - 1),除尽所有因子后若剩余 \(>1\) 则再处理一次,时间复杂度 \(O(n\sqrt{A})\)。 |
| AcWing |
874 筛法求欧拉函数 |
简单 |
⭐⭐⭐⭐ |
线性筛 + 欧拉函数递推 |
在线性筛过程中同步递推欧拉函数:若 \(i\) 为质数则 \(\varphi(i)=i-1\);若 \(p_j \mid i\) 则 \(\varphi(i \cdot p_j)=\varphi(i) \cdot p_j\)(指数+1),否则 \(\varphi(i \cdot p_j)=\varphi(i) \cdot (p_j-1)\)(乘性),最后累加求和,时间复杂度 \(O(n)\)。 |
| 洛谷 |
AT_abc357_d 88888888 |
普及 |
|
|
|
| 洛谷 |
P2303 Longge的问题 |
提高+ |
|
|
|
| 洛谷 |
P2568 GCD |
提高+ |
|
|
|
| 洛谷 |
P5091 扩展欧拉定理 |
提高+ |
|
|
|
| 洛谷 |
P11961 原根判断 |
提高+ |
|
|
|
【欧几里得算法】
算法模板
// 最大公约数
int gcd(int a, int b)
{
return b ? gcd(b, a % b) : a;
}
// 最小公倍数
a * b / gcd(a, b)
// 预处理所有数的最大公约数
for (int d=1; d<MAXA; d++)
for (int i=d; i<MAXA; i+=d)
ans[i] = max(ans[i], d); // 更新ans[i]为最大的d
// 系统库函数
__gcd(a, b)
算法应用
| 题目来源 |
标题 |
难度 |
星级 |
考察算法 |
一句话思路总结 |
| AcWing |
872 最大公约数 |
简单 |
⭐ |
欧几里得算法(辗转相除法) |
利用核心定理 \(\gcd(a, b) = \gcd(b, a \bmod b)\) 递归求解,终止条件为 \(b = 0\) 时返回 \(a\),单次时间复杂度 \(O(\log(\min(a, b)))\),适用于两数 GCD、LCM 及分数约分等基础数论问题。 |
| 洛谷 |
AT_abc463_a 16 9 |
入门 |
|
|
|
| 洛谷 |
P17011 晚宴 |
普及- |
⭐⭐ |
枚举 + 最大公约数 |
双重循环枚举所有 \(C_n^2\) 种菜对,用 __gcd 判断互质(\(\gcd=1\)),更新满足条件的最大和,时间复杂度 \(O(n^2 \log V)\)(\(n \le 1000\)),空间复杂度 \(O(1)\) |
| AtCoder |
AT_awc0101_d Tiling Plan |
|
⭐⭐⭐ |
GCD + 因子枚举 |
先求所有房间 \((H_i, W_i)\) 各自最大公约数的总GCD \(g = \gcd(g_1, g_2, \ldots, g_N)\)(其中 \(g_i = \gcd(H_i, W_i)\)),则合法边长 \(d\) 必为 \(g\) 的因子;枚举 \(g\) 的所有因子并按从大到小排序,对每个候选 \(d\) 验证是否满足 \(\frac{H_i}{d} \times \frac{W_i}{d} \equiv 0 \pmod{S_i}\)(即瓷砖数量为 \(S_i\) 的倍数),第一个满足条件的即为最大边长,时间复杂度 \(O(\sqrt{g} \times N)\),空间复杂度 \(O(\sqrt{g})\)。 |
【扩展欧几里得算法】
算法模板
// 求x, y,使得ax + by = gcd(a, b)
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 exgcd(int a, int b, int &x, int &y)
{
if (!b)
{
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;
}
算法应用
| 题目来源 |
标题 |
难度 |
星级 |
考察算法 |
一句话思路总结 |
| AcWing |
877 扩展欧几里得算法 |
简单 |
⭐⭐⭐ |
扩展欧几里得算法(exgcd) |
递归求解到边界 \(b=0\) 时 \(x=1,y=0\),回溯时利用递推关系 \(x=y_1,\; y=x_1-\lfloor a/b \rfloor \cdot y_1\) 由下层解构造上层解,从而构造性证明裴蜀定理并求出满足 \(ax+by=\gcd(a,b)\) 的一组整数解,时间复杂度 \(O(\log(\min(a,b)))\)。 |
| AcWing |
878 线性同余方程 |
|
|
|
|
| 洛谷 |
P1082 同余方程 |
普及+ |
|
|
|
【中国剩余定理】
算法模板
// 董晓的板子,比较好理解,但是对于大数会WA,需要修改int的范围
int exgcd(int a, int b, int &x, int &y)
{
if (!b)
{
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;
}
int CRT(int m[], int r[])
{
int M = 1, ans = 0;
for (int i=1; i<=n; i++) M*=m[i];
for (int i=1; i<=n; i++)
{
int c = M/m[i], x, y;
exgcd(c, m[i], x, y);
ans = (ans + r[i]*c*x%M)%M;
}
return (ans%M+M)%M;
}
算法应用
| 题目来源 |
标题 |
难度 |
星级 |
考察算法 |
一句话思路总结 |
| AcWing |
204 表达整数的奇怪方式 |
中等 |
⭐⭐⭐⭐ |
中国剩余定理(CRT)+ 扩展欧几里得算法 |
将 \(n\) 个同余方程 \(x \equiv m_i \pmod{a_i}\) 两两合并,每次用 exgcd 求解 \(a_1 k_1 + a_2 k_2 = m_2 - m_1\),若 \((m_2-m_1) \bmod \gcd(a_1,a_2) \neq 0\) 则无解,否则更新合并结果为 \(x \equiv m_1 \pmod{\text{lcm}(a_1,a_2)}\),最终得到最小非负解,时间复杂度 \(O(n \log A_{\max})\)。 |
| 洛谷 |
P1495 中国剩余定理(CRT) |
提高+ |
|
|
|
【高斯消元】
算法模板
// a[N][N]是增广矩阵
int gauss()
{
int c, r;
for (c = 0, r = 0; c < n; c ++ )
{
int t = r;
for (int i = r; i < n; i ++ ) // 找到绝对值最大的行
if (fabs(a[i][c]) > fabs(a[t][c]))
t = i;
if (fabs(a[t][c]) < eps) continue;
for (int i = c; i <= n; i ++ ) swap(a[t][i], a[r][i]); // 将绝对值最大的行换到最顶端
for (int i = n; i >= c; i -- ) a[r][i] /= a[r][c]; // 将当前行的首位变成1
for (int i = r + 1; i < n; i ++ ) // 用当前行将下面所有的列消成0
if (fabs(a[i][c]) > eps)
for (int j = n; j >= c; j -- )
a[i][j] -= a[r][j] * a[i][c];
r ++ ;
}
if (r < n)
{
for (int i = r; i < n; i ++ )
if (fabs(a[i][n]) > eps)
return 2; // 无解
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; // 有唯一解
}
算法应用
| 题目来源 |
标题 |
难度 |
星级 |
考察算法 |
一句话思路总结 |
| AcWing |
883 高斯消元解线性方程组 |
简单 |
⭐⭐⭐⭐ |
高斯消元法(列主元) |
通过列主元策略选取当前列绝对值最大元素作为主元,经初等行变换将增广矩阵化为行阶梯形,若有效方程数等于变量数则回代求解唯一解,若存在矛盾方程则无解,若有效方程数小于变量数则无穷多解,时间复杂度 \(O(n^3)\)。 |
| AcWing |
884 高斯消元解异或线性方程组 |
|
|
|
|
| 洛谷 |
P2455 线性方程组 |
提高+ |
|
|
|
| 洛谷 |
[[P3389 高斯消元法]] |
提高+ |
|
|
|
【矩阵乘法】
算法模板
算法应用
【排列组合】
算法模板
递推法求组合数
// c[a][b] 表示从a个苹果中选b个的方案数
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] + c[i - 1][j - 1]) % mod;
通过预处理逆元的方式求组合数
// 首先预处理出所有阶乘取模的余数fact[N],以及所有阶乘取模的逆元infact[N]
// 如果取模的数是质数,可以用费马小定理求逆元
int qmi(int a, int k, int p) // 快速幂模板
{
int res = 1;
while (k)
{
if (k & 1) res = (LL)res * a % p;
a = (LL)a * a % p;
k >>= 1;
}
return res;
}
// 预处理阶乘的余数和阶乘逆元的余数
fact[0] = infact[0] = 1;
for (int i = 1; i < N; i ++ )
{
fact[i] = (LL)fact[i - 1] * i % mod;
infact[i] = (LL)infact[i - 1] * qmi(i, mod - 2, mod) % mod;
}
// 输出结果
cout << fact[a] * infact[b] % mod * infact[a-b] % mod << endl;
Lucas定理求组合数
// Lucas定理:若p是质数,则对于任意整数 1 <= m <= n,有:
C(n, m) = C(n % p, m % p) * C(n / p, m / p) (mod p)
int p; // 定义p为全局变量
int qmi(int a, int b) // 快速幂模板
{
int mul = 1;
while (b)
{
if (b&1) mul = mul * a % p;
a = a * a % p;
b >>= 1;
}
return mul;
}
int C(int a, int b)
{
int res = 1;
for (int i=1, j=a; i<=b; i++, j--)
{
res = res * j % p; // 处理分子
res = res * qmi(i, p-2) % p; // 处理分母
}
return res;
}
int lucas(int a, int b)
{
if (a<p && b<p) return C(a, b);
return C(a%p, b%p) * lucas(a/p, b/p) % p;
}
分解质因数法求组合数
/*
当我们需要求出组合数的真实值,而非对某个数的余数时,分解质因数的方式比较好用:
1. 筛法求出范围内的所有质数
2. 通过 C(a, b) = a! / b! / (a - b)! 这个公式求出每个质因子的次数。n!中p的次数是 n / p + n / p^2 + n / p^3 + ...
3. 用高精度乘法将所有质因子相乘
*/
int primes[N], cnt; // 存储所有质数
int sum[N]; // 存储每个质数的次数
bool st[N]; // 存储每个数是否已被筛掉
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) // 求n!中p的次数
{
int res = 0;
while (n)
{
res += n / p;
n /= p;
}
return res;
}
vector<int> mul(vector<int> &A, int b) // 高精度乘低精度模板
{
vector<int> C;
int t = 0; //进位
for (int i=0; i<A.size() || t; i++)
{
if (i<A.size()) t+=A[i]*b;
C.push_back(t%10);
t/=10;
}
while (C.size()>1 && C.back()==0) C.pop_back(); //去掉前导0
return C;
}
// 1.预处理范围内的所有质数
get_primes(a);
// 2.求每个质因数的次数
for (int i = 0; i < cnt; i ++ )
{
int p = primes[i];
sum[i] = get(a, p) - get(b, p) - get(a - b, p);
}
// 3.用高精度乘法将所有质因子相乘
vector<int> res;
res.push_back(1);
for (int i = 0; i < cnt; i ++ )
for (int j = 0; j < sum[i]; j ++ )
res = mul(res, primes[i]);
// 4.输出结果
for (int i=res.size()-1; i>=0; i--) cout << res[i];
cout << endl;
卡特兰数
// 给定n个0和n个1,它们按照某种顺序排成长度为2n的序列,满足任意前缀中0的个数都不少于1的个数的序列的数量为:
Cat(n) = C(2n, n) / (n + 1)
// a = 2n, b = n, mod = 1e9+7
int res = 1;
for (int i=a; i>a-b; i--) res = res * i % mod;
for (int i=1; i<=b; i++) res = res * qmi(i, mod-2, mod) % mod;
res = res * qmi(n+1, mod-2, mod) % mod;
算法应用
| 题目来源 |
标题 |
难度 |
星级 |
考察算法 |
一句话思路总结 |
| AcWing |
885 求组合数I |
简单 |
⭐⭐ |
递推法(杨辉三角) |
利用递推公式 \(C(n,k) = C(n-1,k) + C(n-1,k-1)\) 预处理 \(O(N^2)\) 的组合数表(\(N=2005\)),边界 \(C(i,0)=1\),每次查询 \(O(1)\) 查表输出并对 \(10^9+7\) 取模,适用于数据范围小、查询次数多的场景。 |
| AcWing |
886 求组合数II |
简单 |
⭐⭐⭐ |
阶乘 + 逆元(费马小定理 + 快速幂) |
预处理阶乘数组 fact[i]=i! % mod 和阶乘逆元数组 infact[i]=(i!)^{-1} % mod(利用费马小定理 \(i^{-1} \equiv i^{mod-2} \pmod{mod}\) 配合快速幂),查询时直接按公式 \(C(a,b) = fact[a] \cdot infact[b] \cdot infact[a-b] \pmod{mod}\) 计算,预处理 \(O(N \log mod)\)、查询 \(O(1)\),适用于 \(a \leq 10^5\) 的大规模组合数计算。 |
| AcWing |
887 求组合数III |
简单 |
⭐⭐⭐⭐ |
卢卡斯定理(Lucas Theorem)+ 快速幂求逆元 |
利用卢卡斯定理 \(C(a,b) \equiv C(a \bmod p, b \bmod p) \cdot C(\lfloor a/p \rfloor, \lfloor b/p \rfloor) \pmod p\) 将大组合数递归分解为 \(p\) 进制下各位的小组合数,每个小组合数用乘法公式配合费马小定理求逆元 \(O(p)\) 计算,总时间复杂度 \(O(p \cdot \log_p a)\),适用于 \(a,b\) 极大(如 \(10^{18}\))而 \(p\) 为质数的场景。 |
| AcWing |
888 求组合数IV |
简单 |
⭐⭐⭐⭐ |
线性筛 + 勒让德定理 + 高精度乘法 |
先用线性筛预处理 \([2,a]\) 的质数,再用勒让德定理 \(\sum \lfloor n/p^k \rfloor\) 计算每个质数在 \(C(a,b)\) 中的指数(\(C(a,b)\) 的指数为 \(a!\) 中的指数减去 \(b!\) 和 \((a-b)!\) 中的指数),最后用高精度乘小整数将所有质因子按指数乘起来得到精确值,时间复杂度 \(O(a \log a + D \cdot \pi(a))\)。 |
| AcWing |
889 满足条件的01序列 |
简单 |
⭐⭐⭐ |
卡特兰数(Catalan Number)+ 反射原理 + 费马小定理求逆元 |
总排列数 \(C_{2n}^n\) 减去不合法序列数,利用反射原理将不合法序列与 \(C_{2n}^{n-1}\) 建立一一对应,故合法序列数为 \(\text{Cat}(n) = C_{2n}^n - C_{2n}^{n-1} = \frac{(2n)!}{n!(n+1)!}\),分子连乘后乘以分母各因子的模逆元(费马小定理+快速幂)并对 \(10^9+7\) 取模,时间复杂度 \(O(n \log p)\)。 |
| 洛谷 |
AT_abc345_c One Time Swap |
普及- |
|
|
|
| 洛谷 |
P1358 扑克牌 |
普及- |
|
|
|
| 洛谷 |
P5520 青原樱 |
普及- |
|
|
|
| 洛谷 |
AT_abc375_d ABA |
普及 |
|
|
|
| 洛谷 |
AT_abc425_e Count Sequences 2 |
普及 |
|
|
|
| 洛谷 |
[[P10112 奖品分配]] |
普及 |
|
|
|
| 洛谷 |
[[P11250 手套配对]] |
普及 |
|
|
|
| 洛谷 |
AT_abc405_e Fruit Lineup |
普及+ |
|
|
|
| 洛谷 |
AT_abc406_e Popcount Sum 3 |
普及+ |
|
|
|
| 洛谷 |
P4071 排列计数 |
普及+ |
|
|
|
| 洛谷 |
P11362 遗失的赋值 |
普及+ |
|
|
|
【组合概率】
算法模板
算法应用
| 题目来源 |
标题 |
难度 |
星级 |
考察算法 |
一句话思路总结 |
| 洛谷 |
AT_abc463_f Senshuraku |
提高+ |
⭐⭐⭐⭐⭐ |
组合概率 / 分类讨论 |
将选手按当前胜场与最大值 \(mx\) 的关系分类统计配对情况,定义 \(F(c,p) = \sum_{i=0}^{p} C(p,i)\cdot(\frac12)^p\cdot\frac{1}{c+i}\) 为"自由配对中产生 \(i\) 个新最大值选手时的均匀随机选冠军概率期望",分 \(B>0\)(必有 \(mx+1\) 冠军)和 \(B=0\)(可能仍为 \(mx\))两种情况,对每类选手用 \(F\) 函数结合模逆元计算最终概率。 |
【容斥原理】
算法模板
求集合的并集
int calc()
{
for (int i=1; i< (1<<m); i++)
{
int t = 1, cnt = 0;
for (int j=0; j<m; j++)
{
if (i>>j & 1)
{
cnt++;
if (t*p[j]>n)
{
t = -1;
break;
}
t *= p[j];
}
}
if (t != -1)
{
if (cnt%2) res += n/t;
else res -= n/t;
}
}
return res;
}
算法应用
| 题目来源 |
标题 |
难度 |
星级 |
考察算法 |
一句话思路总结 |
| AcWing |
890 能被整除的数 |
简单 |
⭐⭐ |
容斥原理 + 二进制枚举 |
用 \(m\) 位二进制数枚举所有 \(2^m-1\) 个非空质数子集,对每个子集计算质数乘积 \(t\),按子集大小奇偶性对 \(\lfloor n/t \rfloor\) 进行"加奇减偶"的容斥,最终得到能被至少一个质数整除的数的个数,时间复杂度 \(O(2^m \cdot m)\)。 |
| 洛谷 |
P1450 硬币购物 |
提高+ |
|
|
|
【裴蜀定理】
算法模板
int gcd(int a, int b)
{
return b==0?a:gcd(b,a%b);
}
int main(){
cin >> n;
for(int i=1;i<=n;i++)
{
cin >> a;
s = gcd(s,abs(a)); // 裴蜀定理实际上就是求所有系数的最大公约数
}
cout << s;
return 0;
}
算法应用
| 题目来源 |
标题 |
难度 |
星级 |
考察算法 |
一句话思路总结 |
| 洛谷 |
P4549 裴蜀定理 |
普及 |
⭐⭐ |
裴蜀定理 + 欧几里得算法 |
根据裴蜀定理,所有整数线性组合 \(\sum A_i X_i\) 构成的集合恰好是 \(\gcd(A_1, A_2, \ldots, A_n)\) 的整数倍集合,故最小正线性组合即为所有 $ \ |
【博弈论】
算法模板
NIM游戏
// NIM游戏
// 定理: NIM博弈先手必胜,当且仅当 A1 ^ A2 ^ … ^ An != 0 (异或和为0)
int res = 0;
while (n--)
{
int x;
cin >> x;
res ^= x;
}
if (res) cout << "Yes" << endl;
else cout << "No" << endl;
// 台阶NIM游戏
int res = 0;
for (int i=1; i<=n; i++)
{
int x;
cin >> x;
if (i%2) res ^= x;
}
if (res) cout << "Yes" << endl;
else cout << "No" << endl;
有向图游戏的和
/*
设G1, G2, …, Gm 是m个有向图游戏。定义有向图游戏G,它的行动规则是任选某个有向图游戏Gi,并在Gi上行动一步。G被称为有向图游戏G1, G2, …, Gm的和。
有向图游戏的和的SG函数值等于它包含的各个子游戏SG函数值的异或和,即:
SG(G) = SG(G1) ^ SG(G2) ^ … ^ SG(Gm)
定理
有向图游戏的某个局面必胜,当且仅当该局面对应节点的SG函数值大于0。
有向图游戏的某个局面必败,当且仅当该局面对应节点的SG函数值等于0。
*/
// 集合NIM游戏
int sg(int x)
{
if (f[x]!=-1) return f[x];
unordered_set<int> S;
for (int i=0; i<k; i++)
{
int sum = s[i];
if (x>=sum) S.insert(sg(x-sum));
}
for (int i=0; ; i++)
if (!S.count(i))
return f[x] = i;
}
// 拆分NIM游戏
int sg(int x)
{
if (f[x]!=-1) return f[x];
unordered_set<int> S;
for (int i=0; i<x; i++)
for (int j=0; j<=i; j++)
S.insert(sg(i) ^ sg(j));
//mex操作
for (int i=0; ; i++)
if (!S.count(i))
return f[x] = i;
}
算法应用
| 题目来源 |
标题 |
难度 |
星级 |
考察算法 |
一句话思路总结 |
| AcWing |
891 Nim游戏 |
简单 |
⭐⭐ |
博弈论(Nim定理 / Sprague-Grundy定理) |
根据Bouton定理,Nim游戏中先手必胜当且仅当所有堆石子数的异或和不为 \(0\),即计算 \(a_1 \oplus a_2 \oplus \cdots \oplus a_n\),若结果非零则输出 Yes(必胜),否则输出 No(必败),时间复杂度 \(O(n)\)。 |
| AcWing |
892 台阶-Nim游戏 |
|
|
|
|
| AcWing |
893 集合-Nim游戏 |
简单 |
⭐⭐⭐⭐ |
Sprague-Grundy(SG)定理 + 记忆化搜索 |
对每堆石子数 \(x\) 递归计算 SG 函数值 \(\text{sg}(x) = \text{mex}\{\text{sg}(x-s_i) \mid s_i \in S, x \geq s_i\}\)(用记忆化数组避免重复计算),最后将所有堆的 SG 值异或,若结果非零则先手必胜输出 Yes,否则输出 No,时间复杂度 \(O(n \cdot H \cdot k)\)。 |
| AcWing |
894 拆分-Nim游戏 |
简单 |
⭐⭐⭐⭐ |
Sprague-Grundy(SG)定理 + 记忆化搜索 |
对每堆石子数 \(x\) 递归计算 SG 函数值 \(\text{sg}(x) = \text{mex}\{\text{sg}(i) \oplus \text{sg}(j) \mid 0 \leq j \leq i < x\}\)(拆分后两堆独立子游戏的 SG 值异或),用记忆化数组避免重复计算,最后将所有堆的 SG 值异或,若结果非零则先手必胜输出 Yes,否则输出 No,时间复杂度 \(O(H^3)\)(\(H = \max(a_i)\))。 |
| 洛谷 |
[[P1247 取火柴游戏]] |
普及+ |
⭐⭐⭐ |
博弈论 / Nim 博弈 |
计算所有堆大小的异或和 \(s\),若 \(s = 0\) 则先手必败输出 lose,否则找到最小的 \(i\) 使得 \((n_i \oplus s) < n_i\),从第 \(i\) 堆取走 \(n_i - (n_i \oplus s)\) 根使异或和变为 \(0\),时间复杂度 \(O(k)\),空间复杂度 \(O(k)\) |
| 洛谷 |
[[P2197 Nim游戏]] |
普及+ |
|
|
|
| 洛谷 |
P8901 Circular Barn |
普及+ |
|
|
|
| 洛谷 |
[[P10501 Cutting Game]] |
提高+ |
|
|
|
| 洛谷 |
[[P10507 Georgia and Bob]] |
提高+ |
|
|
|
【卢卡斯定理】
算法模板
算法应用
| 题目来源 |
标题 |
难度 |
星级 |
考察算法 |
一句话思路总结 |
| 洛谷 |
[[P3807 卢卡斯定理]] |
提高+ |
|
|
|