acwing算法基础课IV

质数

大于1 的整数中, 只包含1 和 本身这两个约数. 叫做质数或者素数.

d|n,,,,nd|n,,e.g.n=12,3|124|12.

所以我们枚举 较小的那个 有 dnd,, 即 d2n  O(n)

试除法

直接从 试做到.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());
}

筛质数

筛法. 朴素做法

n(12+13+....+1n) 运行次数. 调和级数.

=n(lnn+c) c是欧拉常数. => O(nlogn)

埃氏筛法:

改进. 删掉所有的质数的倍数.

​ 质数定理 1~n中 有 nlnn个质数.

​ 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;

​ 任何一个数一定存在最小质因子, 所以一定会被筛掉.

  1. i % pj == 0

  2. pj 一定是i 的最小质因子, pj 一定是 pj * i 的最小质因子.

  3. i % pj != 0

    1. 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;
}

约数

试除法求约数

d|n,,,,nd|n,,e.g.n=12,3|124|12.

和质因数一样, 到 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;
}
约数个数

约数个数 是算数基本定理.

如果 N=p1a1p2a2...

则 个数是 (a1+1)(a2+1)...(an+1)

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;
}
约数之和

=(p10+p11+p12).... 每一个都乘起来.

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;
}
最大公约数

欧几里得算法.辗转相除法.

d|a d|b => d|(ax+by) a的若干倍+ b的若干倍

(a,b)=(b,a%b)

(a,b)=(b,acb) 所以 根据上面可以的出来.

d|0 永远成立..

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 互质的数的个数.

N=p1a1p2a2pmam

ϕ(N)=N×p11p1×p21p2××pm1pm

这个式子展开和容斥原理的公式是一样的.

img

如何求:

  1. 从1~N中去掉 p1 p2 ... pk 的倍数.
  2. 加上所有 pi * pj的倍数
  3. 去掉所有..... 加上...

容斥原理....

时间复杂度 O(n)

直接根据定义 求 不套用筛法.

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 .

三种情况:

  1. 如果 i 是质数, 有 i - 1 个和他互质
  2. 如果 prime[j] 是 i 的因子, 有 phi[i] * pj -1
  3. 如果 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 互质的话.

aϕ(n),,1(mod,,n)

a1a2aϕ(n)n 互质. 则: aa1,,aa2,,aaϕ(n) 也互质

​ 后面 两两不相同, 且和 n 互质的数 只有 ϕ(n) 个.

两个乘积 同余, 则有 aϕ(n)1

简化版本 如果 n 是 质数的话(费马定理)

ap1,,1(mod,,p)

快速幂

快速幂

快速求出来.

akmod p 在 O(logk) 时间内 求出来, 其中, a, k, p < 109 其实很大了.

反复平方法

将 k 转换成 2进制就行了.

a2logk=(a2logk1)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);
    }
}
快速幂求逆元

通俗来说就是求. bx1(mod,,m)

由 : bm1,,1(mod,,m) 如果 m 是质数 的话, x=bm2

具体看推导吧, 其实就是求 bp2 快速幂.

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 ..

ai×xi+bi×yi=gcd(ai,bi)

gcd\left( a,b,x,y-\frac{a}{b}x \right) =gcd\left( b,a%b,y,x \right)

by+(aabb)x=d => ax+b(yabx)=d

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 以后结果是相同的.

ai×xibi(modmi)

=>ax=my+b

给定, a m b 求出来 x 和 y .

需要让 b 是 a 和 m 的 gcd 的倍数就可以. (a,m)|b

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 就好了呢?

    }
}

中国剩余定理

表达整数的奇怪方式

  1. \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}
  2. let M,,=,,m1m2m3....
  3. Mi=Mmi,,Mi1是逆元
  4. 则: $ x=a_1M_1M_{\mathrm{i}}{-1}+a_2M_2M_{2}+...$
  5. x

a1,a2,,anm1,m2,,mn 求最小的 x

i[1,n],xmi(mod ai)

img

听到了4分多.

Code

求组合数

容斥原理

博弈论

posted @   benenzhu  阅读(36)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示
主题色彩