算法基础课第四章数论
1、分解质因数
#include<iostream> #include<cmath> #include<cstring> using namespace std; int main() { int n; cin >> n; while(n--) { int x; cin >> x; for(int i = 2; i <= sqrt(x);i++) { if(x % i == 0) { int s = 0; while(x % i == 0) { x /= i; s++; } cout << i << " " << s << endl; // k } } if(x > 1) cout << x << " " << 1 << endl; cout << endl; } return 0; }
2、分解约数
void getYue(LL n) { for (int i = 1; i <= n / i; i++) { if (n % i == 0) { if (n != i * i) { cout << i << " " << n / i << endl; } else cout << i << endl; } } }
3、约数个数

约数之和
基本思想
如果\(N = p_1^{c_1} * p_2^{c_2} * ...p_k^{c_k}\)
约束个数 \((c1 + 1)(c2+1)(c3+1)...(ck+1)\)
约数之和 \((1 + p1^1 + ...+ p1 ^ c_1) *...*(1 + pk^1 + ...+pk^{c_k})\)
while (b--) t = (t * a + 1) % mod;
t = t * p + 1
t = 1
t = p + 1
\(t = p ^ 2 + p + 1\)
\(t = p ^ 3 + p ^ 2 + p + 1\)
......
#include<iostream> #include<unordered_map> typedef long long ll; using namespace std; const int mod = 1e9 + 7; int n; int main() { scanf("%d", &n); unordered_map<int,int> primes; while(n--) { int x; scanf("%d", &x); for(int i = 2;i * i <= x;i++) { while(x % i == 0) { x /= i; primes[i]++; } } if(x > 1) primes[x]++; } ll ans = 1; for(auto p : primes) { ll a = p.first, b = p.second; ll t = 1; while(b--) t = (t * a + 1) % mod; // 求单个约s的乘数 ans = ans * t % mod; // 约束之和计算 } cout << ans << endl; return 0; }
5、欧拉函数

欧拉定理:$a ^{\phi(n)} \equiv 1 (modn) $(a、n互质)
1~n中,\(a_1、a_2、..a_\phi(n)\) 是和n互质的数,那么\(aa_1、aa_2、....aa_\phi(n)\)也和n互质,
\(aa_i\equiv aa_j(mod n)\)
6、扩展欧几里得算法
\(ax + by = gcd(a,b)\)
bx1 + (a%b)*y1 = gcd(b,a%b) = gcd(a,b) .....,gcd的值一直是相等的
a % b = a - (a / b) * b,代入
\(bx1 + a * y1 - (a/b) * b* y1\)
\(= a * y1 + b* (x1 - a/b * y1) = ax + by\)
发现,\(x = y1\),\(y = (x_1 - a /b * y_1)\) (x,y是初始的,x1,y1是一步一步gcd变换的)
求逆元
\(ax + my = 1\) ,\(ax = 1 (modm)\) ,\(X\)即为我们要求的逆元。
#include<iostream> using namespace std; void exgcd(int a,int b, int& x,int& y) { if(b == 0) { x = 1, y = 0; return; } exgcd(b, a % b, x, y); int t = y; y = x - (a / b) * y; x = t; } // 第2z void 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); // x放到y的位置那么递归里面y的改变就是在改变x 即x=y1 y -= a / b * x; // y = x1 - a/b*y1 // 因为x和调换了 return d; } int main() { int n; cin >> n; while(n--) { int a, b, x, y; scanf("%d%d", &a, &b); exgcd(a, b, x, y); cout << x << " " << y << endl; } return 0; }
8、快速幂求逆元
逆元的定义
费马小定理:如果p是一个质数,而整数a 不是 p 的倍数,则有 \(a^{p-1}\equiv 1(mod p)\)
乘法逆元:若整数b, m 互质,并且对于任意的整数 a, 如果满足b|a
,则存在一个整数 x, 使得 \(a/b \equiv a * x(mod m)\), 称 x 为 b 的模 m 的乘法逆元,记为 \(b^{-1}(mod m)\)。
\(a/b = a * x(mod m)\) \(a/b=a * b^{-1} (mod m)\) \(1 = b^{-1}*b (mod m)\)
根据费马小定理 \(b^{m-1} \equiv 1 (mod m)\)
\(b^{-1} * b \equiv 1(mod m)\)
\(b^{m-1} = b ^ {-1} * b\) 得 \(b^{-1} = b ^ {m-2}\)
当b与m互质时, b 的乘法逆元为 \(b^{m-2}\)
当b 为 m 的倍数时, b 的逆元不存在
#include<iostream> using namespace std; typedef long long LL; LL get_pow(int a, int b, int p) { LL ans = 1; while(b) { if(b & 1) { ans =(LL)ans * a % p; // 注意LL } a =(LL)a * a % p; // 注意LL类型 模上p之后还是int范围内 b >>= 1; } return ans; } int main() { int n; scanf("%d", &n); while(n--) { int a, p; scanf("%d%d", &a, &p); if(a % p == 0) puts("impossible"); else cout << get_pow(a,p - 2, p) << endl; } return 0; }
9、求组合数
直接求法
#include<iostream> using namespace std; int C(int a, int b) { int ans = 1; for(int i = 1; i <= b; i++) { ans = ans * (a - b + i) / i; } return ans; }
递推法
#include<iostream> using namespace std; const int mod = 1e9 + 7; typedef long long LL; LL f[2010][2010]; int main() { // 预处理 for(int i = 0; i <= 2000; i++) for(int j = 0; j <= i;j++) { // 注意j <= i if(j == 0) f[i][j] = 1; else f[i][j] = (f[i-1][j-1] + f[i-1][j]) % mod; } int n; cin >> n; while(n--) { int a, b; cin >> a >> b; cout << f[a][b] << endl; } return 0; } // f[0][0] = 1 f[1][1]=f[0][1]+f[0][0]=1 f[2][2]=f[1][2]+f[1][1]=1....
逆元法 \(1\leq b\leq a \leq 10^5\)
#include<iostream> using namespace std; typedef long long LL; const int N = 100010, mod = 1e9 + 7; int fact[N], infact[N]; int get_pow(int a, int b, int mod) { int ans = 1; while(b) { if(b & 1) ans = (LL)ans * a % mod; // 注意这里要加上LL a = (LL)a * a % mod; // 加上LL b >>= 1; } return ans; } int main() { 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] * get_pow(i, mod - 2, mod) % mod; } int n; cin >> n; while(n--) { int a, b; scanf("%d%d", &a,&b); printf("%d\n", (LL)fact[a] * infact[b] % mod * infact[a - b] % mod); } return 0; }
10、高精度+筛素数 组合数
思路: 通过计算\(a!\)和\(b!\)、\((a-b)!\)中不同素数 p 的次数,用分子的次数减去分母的次数就是该素数的总次数,最后用高精度乘法计算结果即可。
比如说\(8! = 8 * 7 * 6 * 5 * 4 * 3 * 2 * 1\),8 / 2 = 4,即2、4、6、8这几个是2的倍数, 8 / 4 = 2,即4、8是4的倍数,8 / 8 = 1,8是8的倍数。但是\(p^3\)也是\(p^2\)的倍数,\(p^2\) 也是p的倍数,即2算了1次,4算了两次,8算了3次。
比如 8 / 3 = 2,说明3、6是3的倍数,8 / 9 = 0, 3的平方大于8了,质数3总共出现2次。
#include<iostream> #include<vector> #include<algorithm> using namespace std; const int N = 5010; 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] * i <= n;j++) { st[primes[j] * i] = 1; if(i % primes[j] == 0) break; } } } // 求n!包含质因子p的个数 int get(int n, int 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(); i++) { t += a[i] * b; c.push_back(t % 10); t /= 10; } while(t) { c.push_back(t % 10); t /= 10; } return c; } int main() { int a, b; cin >> a >> b; get_primes(a); for(int i = 0;i < cnt;i++) { int p = primes[i]; // 素数 sum[i] = get(a,p) - get(b,p) - get(a-b,p); // 这个素数对应的次数(分子减去分母的) } vector<int> res; res.push_back(1); for(int i = 0; i < cnt;i++) for(int j = 0; j < sum[i]; j++) // primes[i]的次数 res = mul(res, primes[i]); for(int i = res.size() - 1; i >= 0;i--) cout << res[i]; puts(""); return 0; }
11、中国剩余定理
题目 整数的奇怪方式
m1、m2、,.....mk两两互质, \(x \equiv a_1(mod m_1)\) \(x \equiv a_2(modm_2)\) .....\(x \equiv a_k(mod m_k)\)
求解: 令\(M=m_1m_2....m_k\),\(M_i = \frac{M}{m_i}\),
$x = a_1 * M_1 * M_1^{-1} + a_2M_2M_2^{-1} +...a_kM_kM_k^{-1} $
其中\(M_i^{-1}\)是\(M_i\)模\(m_i\)的逆
表达整数的奇怪方式
思路:通过两个等式合并成为一个等式,然后继续与下一个等式合并
- 有解等价于 \((a_1,a_2) | (m_2,m_1)\) 其中\(()\)表示最大公约数, \([]\) 表示最小公倍数
- \(k_1 + k (a2/d)、 k_2 + k(a_1 / d)\)是方程组的通解,求最小的\(k_1\) ,如果通过 exgcd求出了\(k_1\) ,$k_1 mod t+ t $是最小的k1 (即k取0)。
- 合并之后的方程 \(x = ka + x_0\) , \(x_0\)就是x $a_1k_1 + m_1 $。
c++中的余数可能为负数:比如 \(-5 mod 3 = -2\) ,但在数学中余数是正的余数为1。
#include<iostream> #include<cmath> using namespace std; typedef long long LL; int n; LL exgcd(LL a, LL b, LL& x, LL& y) { if(!b) { x = 1, y = 0; return a; } int d = exgcd(b, a % b, x, y); int t = y; y = x - a / b * y; x = t; return d; } int main() { int n; bool flag = true; cin >> n; LL a1, m1; cin >> a1 >> m1; for(int i = 1; i < n;i++) { LL a2, m2, k1, k2; cin >> a2 >> m2; LL d = exgcd(a1, a2, k1, k2); if((m2 - m1) % d) { flag = false; break; } k1 = k1 * (m2 - m1) / d; LL t = abs(a2 / d); k1 = (k1 % t + t) % t; // 最小的k1 m1 = k1 * a1 + m1; a1 = (a1 / d * a2); // abs(a1 /d * a2) 可加可不加 } if(!flag) puts("-1"); else cout << (m1 % a1 + a1) % a1 << endl; // 用m1 = k1 * a1 + m1,求出右边的m1c return 0; }
12、公约数
给定两个正整数 a 和 b,你需要回答q 个询问,每个询问给定两个整数l,r,你需要找到最大的整数x,满足
1、x 是 a 和 b 的公约数
2、l <= x <= r
思路: a b 的公约数一定是其最大公约数的约数
求其最大公约数的约数即可,将其排序。使用二分查找是否存在这个约数
// 求这个数的约数: a b 的公约数一定是最大公因数的约数 #include<iostream> #include<algorithm> using namespace std; const int N = 1000010; int cnt; int yue[N]; // 存约数 int gcd(int a, int b) { return b ? gcd(b, a%b): a; } // 求约数 void divide(int x) { yue[cnt++] = 1; for(int i = 2; i * i <= x;i++) { if(x % i == 0) { yue[cnt++] = i; if(i * i != x) yue[cnt++] = x / i; } } yue[cnt++] = x; } int main() { int a, b; scanf("%d%d", &a, &b); int t = gcd(a, b); divide(t); sort(yue, yue + cnt); int q; scanf("%d", &q); while(q--) { int l, r; scanf("%d%d", &l, &r); // 范围区间 // 二分找公约数 int ll = 0, rr = cnt - 1; if(r < 1 || l > yue[rr]) { cout << "-1" << endl; continue; } while(ll < rr) { int mid = ll + rr + 1>> 1; // 注意要加上1 因为l = mid + 1 if(yue[mid] < l) ll = mid + 1; else if (yue[mid] >= l && yue[mid] <= r) ll = mid; // 找的是[l,r]间最大的数 else rr = mid - 1; } if(yue[ll] < l || yue[ll] > r) puts("-1"); else cout << yue[ll]<<endl; } return 0; }
13、容斥原理
14、排列组合的一些公式
15、博弈论
先手必胜状态:可以走到某一个必败状态,可以让对面必败
先手必败状态:可以走到的所有下一状态都是必胜的,对方必胜
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本