boolis_primes(int x){ if (x < 2) returnfalse; // 2是最小的质数,如果n小于2,那n肯定就不是质数 // 从最小的质数2开始枚举到sqrt(n) // 约数成对出现,只用枚举每对较小的一个约数 // i <= n / i 比 sqrt(n)快,同时相对于i * i <= n可以防止溢出 for (int i = 2; i <= x / i; ++ i) //如果可以被i整除,说明这个数不是质数 if(x % i == 0) returnfalse; returntrue; } intmain(){ cin >> n; while (n--){ int x; scanf("%d", &x); if(is_primes(x)) puts("Yes"); elseputs("No"); } return0; }
Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
# 质数的判定——试除法 defmain(): n = int(input()) defis_primes(x): if x < 2: returnFalse i = 2 while i <= x // i: # 注意整除 ifnot x % i: returnFalse i += 1 returnTrue for _ inrange(n): x = int(input()) print("Yes"if is_primes(x) else"No") if __name__ == "__main__": main()
分解质因数——试除法
前置知识
整数分解的唯一性定理
任意一个正整数a都能分解成质数乘积的形式,并且此表示是唯一的
$$
a = P_1{\alpha_1}P_2{\alpha_2} \ldots P_t^{\alpha_t}
$$
# AcWing 867. 分解质因数 # 试除法 defmain(): n = int(input()) defdivide(x): i = 2 while i <= x // i: if x % i == 0: s = 0 while x % i == 0: x //= i # 注意整除 s += 1 print("{:d} {:d}".format(i, s)) i += 1 if x > 1: print("{:d} {:d}".format(x, 1)) print() for _ inrange(n): x = int(input()) divide(x) if __name__ == "__main__": main()
vector<int> get_divisors(int n){ vector<int> res; for (int i=1; i <= n / i; ++ i) if (n % i == 0){ res.push_back(i); // 避免i * i = n时重复 if (i != n / i) res.push_back(n / i); } sort(res.begin(), res.end()); return res; }
intmain(){ int n; cin >> n; while(n--){ int x; cin >> x; auto res = get_divisors(x); for (auto t : res) cout << t << ' '; cout << endl; } return0; }
#include<iostream> #include<algorithm> #include<unordered_map> usingnamespace std; typedeflonglong LL; constint mod = 1e9 + 7; intmain(){ int n; cin >> n; unordered_map<int, int> primes;// 存储每个质因子的次数 // 使用数值直接存储浪费空间,使用map存储 while (n --){ int x; cin >> x; // 试除法分解质因子 for (int i = 2; i <= x / i; ++ i) while (x % i == 0){ x /= i; primes[i] ++; } if (x > 1) primes[x] ++; } LL res = 1; for (auto prime : primes) res = res * (prime.second + 1) % mod; // map容器的迭代器里面有first()和second(),first()相当于健的迭代器,second()相当于值的迭代器 // 取模时利用性质: a*b mod n = (a mod n) * (b mod n) mod n = (a mod n) * b mod n; // 这里分别处理每个正整数即可,不需要先乘在一起 cout << res << endl; return0; }
intmain(){ int n; cin >> n; unordered_map<int, int> primes; while (n--){ int x; cin >> x; // 试除法分解质因子 for (int i = 2; i <= x / i; ++ i) while (x % i == 0){ x /= i; primes[i]++; // 记录次幂数 } if (x > 1) primes[x ++]; } LL res = 1; for (auto prime : primes){ int p = prime.first, a = prime.second; LL t = 1; // t = 1 --> t*p + 1 = p + 1 while (a--) t = (t * p + 1) % mod; // 利用递推式求和 // a * b % p = (a % p) * b % p = (a % p) * (b % p) % p // (a + b) % p = (a % p + b % p) % p res = res * t % mod; } cout << res << endl;
return0; }
欧几里得算法(辗转相除法)
欧几里得算法又称辗转相除法,是指用于计算两个非负整数a,b的最大公约数。应用领域有数学和计算机两个方面。计算公式gcd(a,b) = gcd(b,a mod b)
$O(logn)$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
#include<iostream>
using namepsace std;
intgcd(int a, int b){ return b ? gcd(b, a % b) : a; // 递归写法 }
intmain(){ int n; scanf("%d", &n); while(n--){ int a, b; scanf("%d%d", &a, &b); printf("%d\n", gcd(a, b)); } return0; }
最小公倍数 = 两数相乘 / 最大公约数
1 2 3 4 5 6 7 8 9 10
# 求两个数的最大公约数 # 也可以调库 from math import gcd defgcd(a, b): if b == 0: return a return gcd(b, a % b)
# 求两个数的最小公倍数 deflcm(a, b): return a * b // gcd(a, b)
intmain(){ int n; cin >> n; while(n--){ int a; cin >> a; int res = a; // 试除法分解质因数 // 欧拉公式fa(n) = N * p1 * (p1 - 1) * p2 * (p2 - 1)... * pn * (pn -1) 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);
// 快速幂求逆元 #include<iostream> #include<algorithm> usingnamespace std; typedeflonglong LL; // a^k % p 快速幂 intqmi(int a, int k, int p){ int res = 1; while(k){ if (k & 1) res = (LL) res * a % p; k >>= 1; a = (LL) a * a % p; } return res; }
intmain(){ int n; scanf("%d", &n); while (n--){ int a, p; scanf("%d\n", &a, &p);
int res = qmi(a, p-2, p);// p-2=0时比较特殊,一定返回1 if (a % p) printf("%d\n", res);// 要判断a与p是否互质 elseputs("impossible"); } return0; }
快速幂
在$O(logk)$复杂度内快速求出$ a^k \ mod \ p $的结果($ 1 \leq a, p, k \leq 10^9$)
// a^k % p intqmi(int a, int k, int p){ int res = 1 % p; while(k){ if (k & 1) res = (LL)res * a % p; // 截取k的二进制表示中的最后一位 // 按位与运算有两种典型用法,一是取一个位串信息的某几位,如以下代码截取x的最低7位:x & 0177。二是让某变量保留某几位,其余位置0,如以下代码让x只保留最低6位:x = x & 077。以上用法都先要设计好一个常数,该常数只有需要的位是1,不需要的位是0。用它与指定的位串信息按位与。 k >>= 1; // k右移除2 // 右移运算将一个位串信息向右移指定的位,右端移出的位的信息被丢弃。例如12>>2,结果为3。与左移相反,对于小整数,每右移1位,相当于除以2 a = (LL)a * a % p; } return res; }
intmain(){ int n; cin >> n; while(n--){ int a, k , p; scanf("%d%d%d", &a, &k, &p); printf("%d", qmi(a, k ,p)); } return0; }
裴蜀定理
对于任意正整数a,b,一定存在正整数x,y,使得ax + by = (a,b),且其为a和b能表示出的最小的正整数。(a,b)表示a和b的最大公约数。x,y不唯一。
扩展欧几里得算法
由(a,b) = (b,a % b),则ap + bq = by + (a mod b)x = (a, b)。又$a \ mod\ b = a - \lfloor \frac{a}{b}\rfloor * b$,得公式:
$$ax + b(y - \lfloor\frac{a}{b}\rfloor * x) = (a , b)$$
// 使用递归法 intexgcd(int a, int b, int &x, int &y){ if(!b) // 边界:b=0时,可以找到一组特解:x = 1, y = 0 { x = 1, y = 0; return a; } int d = exgcd(b, a % b, y, x); // 递归求最大公约数 y -= a / b * x;// 利用公式更新y return d; }
intmain(){ int n; scanf("%d", &n);
while (n--){ int a, b ,x, y; scanf("%d%d", &a, &b); exgcd(a, b, x, y); printf("%d %d\n", x, y); } }
应用:求解线性同余方程
线性同余方程:已知$a * x\equiv b \ (mod \ m) $,求x
该方程等价于存在整数y,使得$a * x = m * y + b$,即存在整除y’,使得$ax+my’= b$。使用扩展欧几里得算法即可解决。
// 扩展欧几里得算法 intexgcd(int a, int b, int &x, int &y){ if(!b){ x = 1, y = 1; return a; } int d = exgcd(b, a % b, y, x); y -= a/ b * x; return d; }
intmain(){ int n; scanf("%d", &n); while(n--){ int a, b ,x, y; scanf("%d%d%d", &a, &b, &m); int d = exgcd(a, m, x, y); if (b % d) puts("impossible"); elseprintf("%d\n",(LL)x * (b / d) % m); // 注意系数的变化 } return0; }
constint N = 110; constdouble eps = 1e-6; int n; double a[N][N];
intgauss(){ 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; // 为0的话就无需处理
for (int j = c; j <= n; ++j) swap(a[t][j], a[r][j]); // 将绝对值最大的行换到最顶端 for (int j = n; j >= c; --j) a[r][j] /= 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) return2; // 无解 return1; // 有无穷多组解 } 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]; return0; // 有唯一解 }
intmain(){ cin >> n; for (int i = 0; i < n; ++i) for (int j = 0; j < n + 1; ++j) cin >> a[i][j]; int t = gauss(); if (t == 0) { for (int i = 0; i < n; ++ i) { if (fabs(a[i][n]) < eps) a[i][n] = 0; printf("%.2lf\n", a[i][n]); } } elseif (t == 1) puts("Infinite group solutions"); elseputs("No solution"); return0;
typedeflonglong LL; constint N = 100010, mod = 1e9 + 7;
int fact[N], infact[N];
// 快速幂求逆元 涉及除法的求余要用逆元替代除法 intqmi(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; }
intmain(){ // // 预处理所有阶乘的余数和阶乘逆元的余数 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;// 费马小定理要求mod为质数 } int n; scanf("%d", &n); while(n--){ int a, b; scanf("%d%d", &a, &b); printf("%d\n",(LL)fact[a] * infact[b] % mod * infact[a - b] % mod); } return0; }
Lucas定理求组合数
若p是质数,则对于任意整数 1 <= m <= n,有:
$$
C^b_a\equiv C^{b, mod,p}{a, mod, p} * C^{b/p}{a/p}\ (mod \ p)
$$
适用于p取值范围较大又不太大,适用范围:
$$
10组,1 \leq b \leq a \leq10^{18}, 1\leq 10^5
$$
// 分解质因数 int primes[N], cnt; // 存储所有质数 int sum[N]; // 存储每个质数的次数 bool st[N]; // 存储每个数是否已被筛掉
voidget_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;// 使用最小质因子进行筛选 } } }
intget(int n, int p)// 求n!中的次数 { 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); // 只保留10的余数 t /= 10;//上移一位 }
while (t) { c.push_back(t % 10); t /= 10; }
return c; }
intmain(){ 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 ) res = mul(res, primes[i]); for (int i = res.size() - 1;i >= 0; -- i) printf("%d", res[i]); puts(""); return0; }
intqmi(int a, int k ,int p){ int res = 1; while(k){ if (k & 1) res = (LL)res * a % p; k >>= 1; a = (LL)a * a % p; } return res; }
intmain(){ int n; cin >> n;
// 使用普通的组合数定义公式+逆元求组合数 int a = 2 * n, b = n;
int res = 1;
for (int i = a, j = 1; j <= b; -- i, ++ j) { res = (LL)res * i % mod; res = (LL)res * qmi(j, mod -2, mod) % mod; } res = (LL)res * qmi(n + 1, mod - 2, mod) % mod;// 别忘了1/n+1
intmain(){ cin >> n >> m; for (int i = 1; i < m; ++ i) cin >> p[i];
int res = 0; // 容斥原理公式的$2^n-1$个项对应于$2^n-1$种选法,其中偶数个项的为正,奇数个项的为负 // 使用位运算进行枚举,把i看成m位的二进制数 // 1 << m 表示1的二进制左移m位,当其再转换为十进制数时即表示$2^m$,注意这里i从1开始(至少选一项) for (int i = 1; i < 1 << m; ++ i){ int t = 1, cnt = 0; // t 表示当前所有质数的乘积 // cnt 表示当前选法有几个项(对应于几个集合) for (int j = 0; j < m; ++ j) if (i >> j & 1){ // >>:将一个数的各二进制位右移N位,移到右端的低位被舍弃,对于无符号数,高位补0。 // i >> j & 1:i的二进制中,从右往左数,取第j+1位上的值 cnt ++; if ((LL)t * p[j] > n) { t = -1; // 说明此选法不成立 break; } t *= p[j]; // 对应于该选法中含有p[j]这一项 } if (t != -1){ // 求倍数个数使用 n / t if (cnt % 2) res += n / t; else res -= n / t; // 由项数就奇偶判断正负,偶数个项的为正,奇数个项的为负 } } cout << res << endl;
return0; }
constint N = 20;
int n, m; int p[N];
intmain(){ cin >> n >> m; for (int i = 0; i < m; ++ i) cin >> p[i];
int res = 0; for (int i = 0; i < 1 << m; ++ i){ int t = 1, cnt = 0; for (int j = 0; j < m; ++ j) if (i >> j & 1) { cnt ++; if ((LL)t * p[j] > n){ t = -1; break; } t *= p[j]; } if (t != -1){ if (cnt % 2) res += n / t; else res -= n / t; } } cout << res << endl; return0; }
博弈论
Nim游戏
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
#include<iostream> #include<algorithm>
using namesapce std;
intmain(){ int n; int res = 0; scanf("%d", &n); while(n--){ int x; scanf("%d", &x); res *=x; } if (res) puts("Yes"); elseputs("No");
isng namespace std; cosnt int N = 110, M = 10010; int n, m; int s[N], f[M];
intsg(int x){ if (f[x] != -1) return f[x]; unordered_set<int> S; for (itn i = 0; i < m; ++ i) { int sum = s[i]; if ( x >= sum) S.insert(sg(x - sum)); } for (int i = 0; ; ++ i) if (!S.coutn(i)) return f[x] - i; }
intmain(){ cin >> m; for (int i = 0; i < m; ++ i) cin >> s[i]; cin >> n;
memset(f, -1, sizeof f);
int res = 0; for (int i = 0; i < n; ++ i) { int x; cin >> x; res *=sg(x); } if (res) puts("Yes"); elseputs("No");