acwing算法基础课IV
质数
大于1 的整数中, 只包含1 和 本身这两个约数. 叫做质数或者素数.
所以我们枚举 较小的那个 有 即
试除法
直接从 试做到.sqrt(n)
Code
bool is_prime(int n) {
if (n < 2) return false;
for (int i = 2; i <= n / i; i++) {
if (n % i == 0) return false;
}
return true;
}
int main() {
int n = read();
rep(i, 0, n) puts(is_prime(read()) ? "Yes" : "No");
}
分解质因数
n
中最多只包含一个大于sqrt(n)
的质因子
Code
void print_prime(int n) {
for (int i = 2; i <= n / i; i++) {
if (n % i == 0) {
int s = 0;
while (n % i == 0) s++, n /= i;
// I. 直接暴力判断含有多少个质因数就好了.
O(i), On(s);
}
}
if (n >= 2) O(n), On(1);
// II. 这里输出如果有大于 n 的那个质因子.
puts("");
}
int main() {
int n = read();
rep(i, 0, n) print_prime(read());
}
筛质数
筛法. 朴素做法
运行次数. 调和级数.
c是欧拉常数. => O(nlogn)
埃氏筛法:
改进. 删掉所有的质数的倍数.
质数定理 1~n中 有 个质数.
O(n) O(nloglogn)的复杂度.
Code
bool st[N6];
int cnt_prime(int n) {
int cnt = 0;
for (int i = 2; i <= n; i++) { // I. 注意是 <= n O(n)
if (!st[i]) {
cnt++;
for (int j = i + i;j <= n;j += i) st[j] = true;
// II. i+i 然后每次 +i 排除所有的倍数.
}
}
return cnt;
}
int main() {
cout << cnt_prime(read());
}
线性筛法:
正确性证明: 合数一定会被筛掉.
n 只会被最小质因子筛掉.
当prime[j]
是 i
的最小质因子的时候, 就break;
任何一个数一定存在最小质因子, 所以一定会被筛掉.
-
i % pj == 0
-
pj
一定是i
的最小质因子,pj
一定是pj * i
的最小质因子. -
i % pj != 0
pj
一定小于i
的所有质因子.pj
一定是pj * i
的最小质因子.
对于一个合数
x
, 他一定存在最小的质因子pj
i
在枚举到x
之间, 一定会枚举到x / pj
x / pj
这个数最小质因子一定是pj
所以一定要枚举到pj
来, 所以x
一定会被筛掉.代码写的有点晕这里..
Code
bool st[N6];
int prime[N6];
int cnt_prime(int n) {
int cnt = 0;
for (int i = 2; i <= n; i++) { //
if (!st[i]) prime[cnt++] = i; // I. 如果没有就加上
for (int j = 0; prime[j] <= n / i; j++) { // II. 中止条件是 prime[j] * i > n;
st[prime[j] * i] = true;
// III. 将 prime[j] * i 设置合数;
if (i % prime[j] == 0) break;
//IV. 如果 prime[j] 是 i 的因数就退出.
// 因为 prime[j + 1] * i 会被 ?? 筛掉
}
}
return cnt;
}
约数
试除法求约数
和质因数一样, 到 sqrt(n)
然后特殊判断一下, 如果 i == n/i
只 push 一个就行
最后还需要对所以约数 sort 一下
Code
int f[N6];
void print_divide(int a) {
int cnt = 0;
for (int i = 1; i <= a / i; i++) {
if (a % i == 0) { // III. 判断一下是否是约数.
f[cnt++] = i;
if (a / i != i) f[cnt++] = a / i; // I. 约数不能重复
}
}
sort(f, f + cnt); // II. 需要排序.
rep(i, 0, cnt) O(f[i]);
puts("");
}
int main() {
int n = read();
rep(i, 0, n) {
int a = read();
print_divide(a);
}
return 0;
}
约数个数
约数个数 是算数基本定理.
如果
则 个数是
int范围内, 约数最多的数 1500个 约数 可能是对的..
直接用 unordered_map 进行存储就行了
Code
const int N = 1e9 + 7;
int main() {
unordered_map<int, int> um; // I. 直接用 um 存就好.
int n = read();
rep(i, 0, n) {
int a = read();
for (int i = 2; i <= a / i; i++) {
if (a % i == 0) {
while (a % i == 0) um[i]++, a /= i; // 每个约数的个数.
}
}
if (a != 1) um[a] ++; // 如果还有 push 进去.
}
long long ans = 1;
for (auto [k, v] : um) {
ans = ans * (v + 1) % N; // 每个直接乘一个就好.
}
O((int)ans);
return 0;
}
约数之和
每一个都乘起来.
while(a--) ..t=p*t+1
如何求上面的.
推导?
Code
const int N = 1e9 + 7;
int main() {
unordered_map<int, int> um;
int n = read();
rep(i, 0, n) {
int a = read();
for (int i = 2; i <= a / i; i++) {
if (a % i == 0) {
while (a % i == 0) um[i]++, a /= i; // 对应关系.
}
}
if (a != 1) um[a] ++;
}
long long ans = 1;
for (auto [k, v] : um) {
long long res = 1;
while (v--) {
res = (res * k + 1) % N; // 如何求 p^n + p^n-1 + ...
}
ans = ans * res % N; // 最后总和 乘一下.
}
O((int)ans);
return 0;
}
最大公约数
欧几里得算法.辗转相除法.
=> a的若干倍+ b的若干倍
所以 根据上面可以的出来.
永远成立..
Code
int gcd(int a, int b) {
return a % b ? gcd(b, a % b) : b;
// 如果 b > a: a % b = a => gcd(b, a)
// a % b != 0 (a, b) 和 (b, a % b) 是 同余的.就好了.
}
到了 数学 II 了, 数学部分还有 8 小时...
动态规划 8 小时..
贪心 3 , 最后 1 小时... gogogo 芭芭拉冲压.
欧拉函数
欧拉函数
1 ~ N 中和 N 互质的数的个数.
这个式子展开和容斥原理的公式是一样的.
如何求:
- 从1~N中去掉 p1 p2 ... pk 的倍数.
- 加上所有
pi * pj
的倍数 - 去掉所有..... 加上...
容斥原理....
时间复杂度
直接根据定义 求 不套用筛法.
Code
// 为什么欧拉函数不是 当前数 减去约数个数呢?
// 因为, 10 和 8 这种数 也是不互质的.
int main() {
int n = read();
rep(i, 0, n) {
int a = read();
int res = a;
for (int i = 2; i <= a / i; i++) {
if (a % i == 0) {
while (a % i == 0) a /= i;
res = res / i * (i - 1); // 直接是容斥原理的式子.
}
}
if (a != 1) res = res / a * (a - 1);
On(res);
}
return 0;
}
筛法求欧拉函数
1~N 每个数的欧拉函数之和.
根据前一个数的欧拉函数求出当前自己.
pj-1
或者直接的 pj
.
三种情况:
- 如果
i
是质数, 有i - 1
个和他互质 - 如果 prime[j] 是
i
的因子, 有phi[i] * pj -1
- 如果 prime[j] 不是 i 的因子, 有
phi[i] * pj
Code
int prime[N6], cnt;
int phi[N6];
bool st[N6];
LL cnt_prime(int n) {
LL ans = 0; // 1 是和他自己互质的...
phi[1] = 1;
for (int i = 2; i <= n; i++) {
if (!st[i]) {
prime[cnt++] = i;
phi[i] = i - 1; // 一个数如果是质数, 每个数都和他互质?
// 包括1 , 1 和任何数互质??
}
for (int j = 0; prime[j] <= n / i; j++) {
st[prime[j] * i] = true;
if (i % prime[j] == 0) {
phi[prime[j] * i] = phi[i] * prime[j];
// II. 如果 没有加新的质因子, 直接乘上就好了
break;
}
phi[prime[j] * i] = phi[i] * (prime[j] - 1);
// III. 如果有新的质因子, 那么答案需要 /pj * (pj - 1). 化简后 就是 pj - 1;
}
}
rep(i, 0, n + 1) ans += phi[i];
return ans;
}
int main() {
int n = read();
cout << cnt_prime(n);
}
欧拉定理
如果 a 和 n 互质的话.
和 n
互质. 则: 也互质
后面 两两不相同, 且和 n 互质的数 只有 个.
两个乘积 同余, 则有
简化版本 如果 n 是 质数的话(费马定理)
快速幂
快速幂
快速求出来.
在 O(logk) 时间内 求出来, 其中, a, k, p < 其实很大了.
反复平方法
将 k 转换成 2进制就行了.
欧拉降幂
不要用qmi(read(), read(), read())
这种写法了...
Code
void qmi(LL a, int b, int p) { // III. 注意 a 一定要是 LL 否则容易爆掉.
LL res = 1;
while (b) {
if (b & 1) res = res * a % p; // 这里是拆分
a = a * a % p; // II. 反复平方法
b >>= 1;
}
On((int)res);
}
int main() {
int n = read();
rep(i, 0, n) {
int a = read(), b = read(), p = read();
qmi(a, b, p);
}
}
快速幂求逆元
通俗来说就是求.
由 : 如果 m 是质数 的话,
具体看推导吧, 其实就是求 快速幂.
b和p需要一定互质.
卢卡斯定理??
费马小定理.
Code
void qmi(LL a, int p) {
if (a % p == 0 || p < 2) {
puts("impossible"); // 特殊判断一下就行. 如果是倍数, 一定没有逆元.
return;
}
LL res = 1;
int b = p - 2; // 直接求 p - 2 次方的快速幂就好了.
while (b) {
if (b & 1) res = res * a % p;
a = a * a % p;
b >>= 1;
}
On((int)res);
}
int main() {
int n = read();
rep(i, 0, n) {
int a = read(), p = read();
qmi(a, p);
}
}
扩展欧几里得算法
扩展欧几里得算法
裴蜀定理: 正整数a,b 一定存在 x,y :
ax+by = gcd(a,b)
是 a 和 b 能凑出来的最小的正整数.
ax+by 一定是 gcd(a,b) 的倍数
构造法: 扩展欧几里得算法.
y -= a / b * x
..
=>
Code
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);
// 这里需要交换, 因为每次 a,b是交换顺序的.
y = y - a / b * x; // 按照上面的推导 可以写出来.
return d;
}
int main() {
int n = read();
rep(i, 0, n) {
int a = read(), b = read(), x, y;
exgcd(a, b, x, y);
O(x), On(y);
}
}
线性同余方程
线性同余方程, mod m 以后结果是相同的.
=>
给定, a m b 求出来 x 和 y .
需要让 b 是 a 和 m 的 gcd 的倍数就可以.
Code
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 = y - a / b * x;
return d;
}
int main() {
int n = read();
rep(i, 0, n) {
int a = read(), b = read(), m = read(), x, y;
LL d = exgcd(a, m, x, y);
if (b % d) puts("impossible");
else On((x * (b / d) % m));
// 这里为什么处理一下 % m 就好了呢?
}
}
中国剩余定理
表达整数的奇怪方式
- let
- 是逆元
- 则: $ x=a_1M_1M_{\mathrm{i}}{-1}+a_2M_2M_{2}+...$
- 有
求最小的
听到了4分多.
Code
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!