个人学习笔记平台,欢迎大家交流

红叶~

Be humble, communicate clearly, and respect others.

算法基础课第四章数论

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、约数个数

QQ浏览器截图20200731215214.png

约数之和

基本思想

如果\(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、欧拉函数

image-20211214153754113

image-20211217195401737

欧拉定理:$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变换的)

image-20220329083808279

求逆元

\(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\)

image-20220321154632358

#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、高精度+筛素数 组合数

image-20211217192436003

思路: 通过计算\(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 $。

image-20211217214446109

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、容斥原理

image-20220321173721750

14、排列组合的一些公式

15、博弈论

先手必胜状态:可以走到某一个必败状态,可以让对面必败

先手必败状态:可以走到的所有下一状态都是必胜的,对方必胜

posted @   红叶~  阅读(66)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
点击右上角即可分享
微信分享提示