加载中...

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;

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

  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 一定会被筛掉.

    代码写的有点晕这里..

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

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

img

如何求:

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

容斥原理....

时间复杂度 \(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 .

三种情况:

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

    }
}

中国剩余定理

表达整数的奇怪方式

  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,,=,,m_1m_2m_3....$
  3. $ M_{\mathrm{i}}=\frac{M}{m_{\mathrm{i}}},,\rightarrow M_{\mathrm{i}}^{-1}\text{}$是逆元
  4. 则: $ x=a_1M_1M_{\mathrm{i}}{-1}+a_2M_2M_{2}+...$
  5. 有 $ 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)$

img

听到了4分多.


求组合数

容斥原理

博弈论

posted @ 2022-02-11 12:51  benenzhu  阅读(45)  评论(0)    收藏  举报