常用算法代码模板与题单 | G - 数学

【数学】

算法模板

算法应用

题目来源 标题 难度 星级 考察算法 一句话思路总结
AcWing 459 螺旋方阵
洛谷 AT_abc361_b Intersection of Cuboids 入门
洛谷 AT_abc407_a Approximation 入门
洛谷 AT_abc423_a Scary Fee 入门
洛谷 AT_abc435_a Triangular Number 入门
洛谷 AT_abc460_b Two Rings 入门
洛谷 B3787 精密计时 入门
洛谷 B4035 美丽数字 入门 取模判定 + 合取条件计数 读入 \(n\) 个正整数,对每个数检查 x % 9 == 0 && x % 8 != 0(是 9 的倍数但不是 8 的倍数),满足则 ans++,在线统计无需存储,时间复杂度 \(O(n)\),空间复杂度 \(O(1)\)
洛谷 B4063 奇数和偶数 入门 取模判定 + 计数器累加 读入 \(n\) 个正整数,用 x % 2 判定奇偶性(为 1 则 odd++,否则 even++),在线统计无需存储,最后输出 oddeven,时间复杂度 \(O(n)\),空间复杂度 \(O(1)\)
洛谷 B4257 图书馆里的老鼠 入门 整数除法与取余 计算 \(y\) 小时内老鼠能完整啃完的书数 \(\lfloor y/x \rfloor\),若有余数则当前正在啃的书也不完整,需再减 1,剩余书数为 \(n - y/x - (y \% x \neq 0 ? 1 : 0)\),时间复杂度 \(O(1)\),空间复杂度 \(O(1)\)
洛谷 P1035 级数求和 入门
洛谷 P1425 小鱼的游泳时间 入门
洛谷 P1888 三角函数 入门
洛谷 P2669 金币 入门
洛谷 P6337 CRNE 入门
洛谷 P9226 糖果 入门
洛谷 P10518 交小西的礼物 入门
洛谷 P10520 榕树之心 入门
洛谷 P11076 单挑 入门
洛谷 P11176 提高成绩 入门
洛谷 P11371 糖果 入门
洛谷 P11532 好成绩 入门
洛谷 AT_abc330_c Minimize Abs 2 普及-
洛谷 AT_abc334_b Christmas Trees 普及-
洛谷 AT_abc346_c Σ 普及-
洛谷 AT_abc362_b Right Triangle 普及-
洛谷 AT_abc368_c Triple Attack 普及-
洛谷 AT_abc390_b Geometric Sequence 普及-
洛谷 AT_abc406_b Product Calculator 普及-
洛谷 AT_abc407_c Security 2 普及-
洛谷 AT_abc417_c Distance Indicators 普及-
洛谷 AT_abc419_c King's Summit 普及-
洛谷 B4004 寻找倍数 普及-
洛谷 [[B4079 金币]] 普及-
洛谷 P1009 阶乘之和 普及-
洛谷 P1011 车站 普及-
洛谷 P1017 进制转换 普及-
洛谷 P1062 数列 普及-
洛谷 [[P1202 黑色星期五]] 普及-
洛谷 P1591 阶乘数码 普及-
洛谷 P2415 集合求和 普及-
洛谷 [[P4122 Blocked Billboard]] 普及-
洛谷 [[P5834 MooBuzz]] 普及-
洛谷 P7993 Lonely Photo 普及-
洛谷 P8814 解密 普及-
洛谷 P9748 小苹果 普及-
洛谷 [[P9941 Even More Odd Photos]] 普及-
洛谷 [[P10131 Majority Opinion]] 普及-
洛谷 AT_abc359_c Tile Distance 2 普及
洛谷 AT_abc379_c Sowing Stones 普及
洛谷 AT_abc379_e Sum of All Substrings 普及
洛谷 AT_abc380_d Strange Mirroring 普及
洛谷 AT_abc383_d 9 Divisors 普及
洛谷 AT_abc392_d Doubles 普及
洛谷 AT_abc393_e GCD of Subset 普及
洛谷 AT_abc397_d Cubes 普及
洛谷 AT_abc400_c 2^a b^2 普及
洛谷 AT_abc402_d Line Crossing 普及
洛谷 B4398 第三题 普及
洛谷 P1023 税收与补贴问题 普及
洛谷 [[P1025 数对划分]] 普及
洛谷 P1033 自由落体 普及
洛谷 [[P1134 阶乘问题]] 普及
洛谷 P2085 最小函数值 普及
洛谷 P3951 小凯的疑惑 普及
洛谷 [[P6067 Moo Volume]] 普及
洛谷 P7960 报数 普及
洛谷 [[P9976 Farmer John Actually Farms]] 普及
洛谷 [[P14974 Chip Exchange]] 普及
洛谷 AT_abc361_f x = a ^ b 普及+
洛谷 AT_abc414_e Count A%B=C 普及+
洛谷 AT_abc418_e Trapezium 普及+
洛谷 P1066 2^k进制数 普及+
洛谷 P1593 因子和 普及+
洛谷 P5684 非回文串 普及+
洛谷 P5691 方程的解数 普及+
洛谷 P9118 幂次 普及+
洛谷 P10136 Cowlendar 普及+
洛谷 [[P10188 Milk Exchange]] 普及+
AtCoder AT_awc0001_a Bacteria Growth Experiment
AtCoder AT_awc0014_c Catch the Last Train
AtCoder AT_awc0020_a Equalizing Water
学而思编程 二元函数 普及进阶 ⭐⭐⭐ 双指针 / 单调性优化枚举 函数 \(f(a,b)\) 关于 \(a\)\(b\) 均单调递增,固定 \(a\) 后用双指针从大到小调整 \(b\)(初始 \(b=10^6\),当 \(f(a,b) \ge n\) 时递减 \(b\) 并更新最小值),利用单调性避免 \(O(10^{12})\) 的暴力枚举,时间复杂度 \(O(10^6)\)\(a,b \le 10^6\))。
学而思编程 更奇怪的照片 普及奠基 ⭐⭐ 数学 / 贪心 统计奇偶数量,通过合并两个奇数为偶数调整比例,使奇偶交替排列的组数最大化。
学而思编程 公平分配 语言基础 ⭐⭐ 数学 / 奇偶性 糖果只有 \(1\) 克和 \(2\) 克两种,总重量为偶数且 \(1\) 克糖果个数为偶数(或没有 \(1\) 克时 \(2\) 克个数为偶数)才能平分。
学而思编程 排位 语言基础 ⭐⭐ 数学 / 分类讨论 小X前面不少于 \(a\) 人、后面不超过 \(b\) 人,按 \(a+b\)\(n\) 的大小关系分三种情况讨论可能的排位数量。
学而思编程 评测队列 普及奠基 ⭐⭐⭐ 贪心 / 流水线调度(Johnson算法简化版) 维护两个变量 \(x\)(编译服务器完成前 \(i\) 个程序的总时间)和 \(y\)(运行服务器完成前 \(i\) 个程序的总时间),每处理第 \(i\) 个程序时:编译完成时间 \(x = x + a_i\),运行完成时间 \(y = \max(x, y) + b_i\)(运行服务器必须等当前程序编译完且自身空闲后才能开始运行),最终答案为 \(y\),时间复杂度 \(O(n)\),空间复杂度 \(O(1)\)
学而思编程 签到积分 语言基础 模拟 连续签到天数为 \(k\) 时当天得 \(\min(k, 7)\) 分,断签时 \(k\) 重置为 \(0\)
学而思编程 青蛙跳 语言基础 数学 \(k\) 次跳跃中每 \(2\) 次为一个周期,净位移为 \(a-b\),奇数步时最后再加一个 \(a\)
学而思编程 汽水兑奖 语言基础 数学 / 模拟 每次喝完汽水累加瓶盖和空瓶,用余数累加方式模拟兑换过程,直到无法再兑换。
学而思编程 奇特的数 普及奠基 数学 矩阵中 \(a_{ij} = i \times j\)\(m\) 出现次数等于满足 \(i \times j = m\)\(1 \le i,j \le n\) 的因子对 \((i, m/i)\) 个数。
学而思编程 调整元素 语言基础 ⭐⭐ 数学 / 奇偶性 每次操作加 \(2\) 不改变奇偶性,故所有元素最终能相等当且仅当它们初始奇偶性全部相同。
学而思编程 文件传输 语言基础 ⭐⭐ 数学 / 贪心 \(k\) 小时内已安装文件的机器数指数增长(每小时翻倍),之后线性增长(每小时增加 \(k\))。
学而思编程 增长或翻倍 语言基础 ⭐⭐ 数学 / 逆向思维 \(t\) 逆向操作:偶数且折半后 \(\geq s\) 则除以 \(2\),否则减 \(1\),直到等于 \(s\)
学而思编程 整除子数组 语言基础 ⭐⭐ 数学 / 枚举 利用"末两位能被 \(4\) 整除则整个数能被 \(4\) 整除"的性质,枚举以每个位置结尾的末两位组合。

【约数】

算法模板

试除法分解质因数

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 高斯消元法]] 提高+

【矩阵乘法】

算法模板

算法应用

题目来源 标题 难度 星级 考察算法 一句话思路总结
洛谷 P1962 斐波那契数列 普及+
洛谷 P2044 随机数生成器 普及+
洛谷 [[P3390 矩阵快速幂]] 普及+
洛谷 P1397 矩阵游戏 提高+

【排列组合】

算法模板

递推法求组合数

// 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 卢卡斯定理]] 提高+
posted @ 2026-06-28 19:31  团爸讲算法  阅读(12)  评论(0)    收藏  举报