acwing算法基础课IV
质数
大于1 的整数中, 只包含1 和 本身这两个约数. 叫做质数或者素数.
$ d|n,,\rightarrow ,,\frac{n}{d}|n,,e.g. n=12, 3|12\rightarrow 4|12. $
所以我们枚举 较小的那个 有 $ d\le \frac{n}{d},,$ 即 \(d^2 \le n\) \(O(\sqrt n)\)
试除法
直接从 试做到.sqrt(n)
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)的质因子
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());
}
筛质数
筛法. 朴素做法
\(n(\frac12+\frac13+....+\frac1n)\) 运行次数. 调和级数.
\(=n(\ln n+c)\) c是欧拉常数. => O(nlogn)
埃氏筛法:
改进. 删掉所有的质数的倍数.
质数定理 1~n中 有 \(\frac{n}{lnn}\)个质数.
O(n) O(nloglogn)的复杂度.
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 != 0pj一定小于i的所有质因子.pj一定是pj * i的最小质因子.
对于一个合数
x, 他一定存在最小的质因子pji在枚举到x之间, 一定会枚举到x / pjx / pj这个数最小质因子一定是pj所以一定要枚举到pj来, 所以x一定会被筛掉.代码写的有点晕这里..
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;
}
约数
试除法求约数
$ d|n,,\rightarrow ,,\frac{n}{d}|n,,e.g. n=12, 3|12\rightarrow 4|12. $
和质因数一样, 到 sqrt(n)
然后特殊判断一下, 如果 i == n/i 只 push 一个就行
最后还需要对所以约数 sort 一下
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;
}
约数个数
约数个数 是算数基本定理.
如果 \(N=p_1^{a1}\cdot p_2^{a2}...\)
则 个数是 \((a_1+1)(a_2+1)...(a_n+1)\)
int范围内, 约数最多的数 1500个 约数 可能是对的..
直接用 unordered_map 进行存储就行了
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;
}
约数之和
\(=(p_1^0+p_1^1+p_1^2)....\) 每一个都乘起来.
while(a--) ..t=p*t+1 如何求上面的.
推导?
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;
}
最大公约数
欧几里得算法.辗转相除法.
\(d|a\) \(d|b\) => \(d|(ax+by)\) a的若干倍+ b的若干倍
\((a,b) = (b,a\%b)\)
\((a,b) = (b,a-c \cdot b)\) 所以 根据上面可以的出来.
\(d|0\) 永远成立..
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 互质的数的个数.
\(N = p_1^{a_1}p_2^{a_2}…p_m^{a_m}\)
\(\phi(N) = N \times \frac{p_1-1}{p_1} \times \frac{p_2-1}{p_2} \times … \times \frac{p_m-1}{p_m}\)
这个式子展开和容斥原理的公式是一样的.

如何求:
- 从1~N中去掉 p1 p2 ... pk 的倍数.
- 加上所有
pi * pj的倍数 - 去掉所有..... 加上...
容斥原理....
时间复杂度 \(O(\sqrt n)\)
直接根据定义 求 不套用筛法.
// 为什么欧拉函数不是 当前数 减去约数个数呢?
// 因为, 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
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 互质的话.
$ a^{\phi \left( n \right)}\equiv ,,1\left( mod,,n \right) $
\(\,\, a_1\,\,a_2\,\,a_{\phi \left( n \right)}\) 和 n 互质. 则: $ aa_1,,aa_2,,aa_{\phi \left( n \right)}$ 也互质
后面 两两不相同, 且和 n 互质的数 只有 \(\phi(n)\) 个.
两个乘积 同余, 则有 $ a^{\phi \left( n \right)} \equiv 1$
简化版本 如果 n 是 质数的话(费马定理)
$ a^{p-1}\equiv ,,1\left( mod,,p \right) $
快速幂
快速幂
快速求出来.
$a^k \text{mod }p $ 在 O(logk) 时间内 求出来, 其中, a, k, p < \(10^9\) 其实很大了.
反复平方法
将 k 转换成 2进制就行了.
\(a^{2^{logk}} = (a^{2^{logk-1}})^2\)
欧拉降幂
不要用qmi(read(), read(), read()) 这种写法了...
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);
}
}
快速幂求逆元
通俗来说就是求. $ b\cdot x\equiv 1\left( mod,,m \right) $
由 : $ b^{m-1}\equiv ,,1\left( mod,,m \right) $ 如果 m 是质数 的话, \(x= b^{m-2}\)
具体看推导吧, 其实就是求 \(b^{p-2}\) 快速幂.
b和p需要一定互质.
卢卡斯定理??
费马小定理.
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);
}
}
扩展欧几里得算法
扩展欧几里得算法
裴蜀定理: \(\forall\)正整数a,b 一定存在 x,y :
ax+by = gcd(a,b) 是 a 和 b 能凑出来的最小的正整数.
ax+by 一定是 gcd(a,b) 的倍数
构造法: 扩展欧几里得算法.
y -= a / b * x ..
\(a_i \times x_i + b_i \times y_i = gcd(a_i, b_i)\)
$ gcd\left( a,b,x,y-\frac{a}{b}x \right) =gcd\left( b,a%b,y,x \right) $
$ by+\left( a-\lfloor \frac{a}{b} \rfloor \cdot b \right) x=d$ => $ ax+b\left( y-\lfloor \frac{a}{b} \rfloor \cdot x \right) =d$
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_i \times x_i \equiv b_i \pmod {m_i}\)
=>\(ax = m\cdot y+b\)
给定, a m b 求出来 x 和 y .
需要让 b 是 a 和 m 的 gcd 的倍数就可以. \((a,m)|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 = 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 就好了呢?
}
}
中国剩余定理
表达整数的奇怪方式
- $ \begin{cases} x\equiv a_1\left( mod,,m_1 \right)\ x\equiv a_2\left( mod,,m_2 \right) ,,\ ...\ x\equiv a_{\mathrm{n}}\left( mod,,m_{\mathrm{n}} \right)\\end{cases}$
- let $ M,,=,,m_1m_2m_3....$
- $ M_{\mathrm{i}}=\frac{M}{m_{\mathrm{i}}},,\rightarrow M_{\mathrm{i}}^{-1}\text{}$是逆元
- 则: $ x=a_1M_1M_{\mathrm{i}}{-1}+a_2M_2M_{2}+...$
- 有 $ x%m,,=,,a_1M_1M_{\mathrm{i}}^{-1}%m_1,,\rightarrow \text{因为} M_2M_3...\text{中都有}m_1$
\(a_1,a_2,…,a_n 和 m_1,m_2,…,m_n\) 求最小的 \(x\)
$ \forall i \in [1,n],x \equiv m_i(mod\ a_i)$

听到了4分多.

浙公网安备 33010602011771号