ACM暑期集训8

今天主要学习了数论,算法模板很多,思想很重要。

1.素数

1)素数定理:不超过 n 的素数的个数 π(n) → n / ln(n)  (这样开数组可以开小点,防止TLE)

2)筛选法素数

埃氏筛:用所有小于等于√n 的数来筛去合数。

bool isprime[MAXN + 1];
void getPrime() {
    memset(isprime, true, sizeof(isprime));
    isprime[0] = isprime[1] = false;
    for(int i = 2; i <= MAXN / i; i++) { //比sqrt()快
        if(isprime[i]) {
            for(int j = i * i; j <= MAXN; j += i) {
                isprime[j] = false;
            }
        }
    }
}

 欧拉筛的:在维护 isprime[] 的同时维护 prime[],用于存储已经找好的素数。遍历 2 ~ n,对于每一个数 i,首先,若 i 是素数,则加入到 prime[],接着遍历 prime[],将 i * prime[j] 筛去,如果 prime[j] 能够整除 i 则停止。

bool isprime[MAXN+1];
int prime[MAXN/10], tot; //防止MLE
void getPrime() {
    memset(isprime, true, sizeof(isprime));
    isprime[0] = isprime[1] = false;
    tot = 0;
    for(int i = 2; i < MAXN; i++) {
        if(isprime[i]) prime[tot++] = i;
        for(int j = 0; j < tot && prime[j] <= MAXN / i; j++) {
            isprime[i * prime[j]]=false;
            if(i % prime[j] == 0) break;
        }
    }
}

2.算术基本定理(唯一分解定理)

算术基本定理:每个大于 1 的正整数都可以被唯一地写成素数的乘积,即 n = p1γ1p2γ2 ... ptγt,其中 p1 < p2 < ... < pt 且都为素数。(证明不作详述)

性质:     n 的约数个数= (γ1 + 1)(γ2 + 1)...(γt + 1)(约数个数定理)   

 n 的约数之和     

= (p10+p11+...+p1γ1)(p20+p21+...+p2γ2)...(pt0+pt1+...+ptγt)     (γi,αi,βi是次方,不会操作,弄的看着不像)

扫描二维码关注公众号,回复: 2524365 查看本文章

设 a = p1α1p2α2...ptαt, b = p1β1p2β2...ptβt,则     

gcd(a, b) = p1min(α1, β1)p2min(α2, β2) ... ptmin(αt, βt),     

lcm(a, b) = p1max(α1, β1)p2max(α2, β2) ... ptmax(αt, βt) ,

来一道题

LightOJ-1236 对于正整数 n,求有多少数对 (a, b) 满足 lcm(a, b) = n 且 1 ≤ a ≤ b ≤ n。n ≤1014,T ≤ 200

分析:

首先不对a,b做限制,忽略a ≤ b

设 n=p1γ1p2γ2 ... ptγt,a=p1α1p2α2...ptαt,b= p1β1p2β2...ptβt

n=a*b=gcd(a,b)*lcm(a,b)

等号两边对比由   γi=max(αi,βi)  可知由 2*γi+1  求和中取法,设为s

考虑a ≤ b

对于符合题意的(i,j),那么(j,i)也一定符合题意,特殊的情况是(n,n)

综上考虑可知结果为1+s/2

3.素因子分解

1)时间复杂度O(sqrt(n))

LL factor[100][2]; //factor[i][0]存底数,factor[i][1]存指数
int cnt;
void getFactors(LL n) {
    cnt = 0;
    for(int i = 2; i <= n / i; i++) {
        if(n % i == 0) {
            factor[cnt][0] = i;
            factor[cnt][1] = 0;
            while(n % i == 0) {
                factor[cnt][1]++;
                n /= i;
            }
            cnt++;
        }
    }
    if(n != 1) {
        factor[cnt][0] = n;
        factor[cnt][1] = 1;
        cnt++;
    }
}

2)时间复杂度O(sqrt(n)/logn),但要提前维护好素数表

LL factor[100][2]; //factor[i][0]存底数,factor[i][1]存指数
int cnt;
void getFactors(LL n) {
    cnt = 0;
    for(int i = 0; i < tot && prime[i] <= n / prime[i]; i++) {
        if(n % prime[i] == 0) {
            factor[cnt][0] = prime[i];
            factor[cnt][1] = 0;
            while(n % prime[i] == 0) {
                factor[cnt][1]++;
                n /= prime[i];
            }
            cnt++;
        }
    }
    if(n != 1) {
        factor[cnt][0] = n;
        factor[cnt][1] = 1;
        cnt++;
    }
}

4.欧几里得算法(辗转相除法)

引理:如果 a = bq + r,那么 gcd(a, b) = gcd(b, r)     

证明:“ e | b 且 e | r ” ←→ “ e | (bq + r) 且 e | b ”,所以 b, r 的公因子与 bq + r, b 的公因子完全相同,所以 gcd(bq + r, b) = gcd(b, r),即 gcd(a, b) = gcd(b, r) 

性质:lcm(a, b) = ab / gcd(a, b)

LL gcd(LL a, LL b) {
    return b == 0 ? a : gcd(b, a % b);
}

LL lcm(LL a, LL b) {
    return a / gcd(a, b) * b; //先除后乘
}

5.拓展欧几里得算法

引理 1:gcd(a, b) 等于 a 和 b 的线性组合中最小的正整数     

证明:首先,设 d 是 a 和 b 的线性组合中最小的正整数,d = ma + nb,设 r = a mod d = a - q(ma + nb) = (1 - qm)a - qnb,则 r 是 a 和 b 的线性组合且 0 ≤ r < d,得 r = 0,因此 d | a,同理 d | b。其次,如果 c | a 且 c | b,那么由 d = ma + nb 可得 c | d,因此 d = gcd(a, b)。

引理 2:所有 a 和 b 的线性组合都是 gcd(a, b) 的倍数,所有 gcd(a, b) 的倍数都是 a 和 b 的线性组合。     

证明:① 设 d = gcd(a, b),则 d | a 且 d | b,所以对于任意整数 r, s,都有 d | (ra + sb)。     ② 设 d = gcd(a, b),由引理 1,d = ma + nb,所以对于任意整数 j,都有 jd = (jm)a + (jn)b。

结论:丢番图方程 ax + by = C 有解当且仅当 gcd(a, b) | C(c被gcd(a,b)整除)

设 d = gcd(a, b),先解方程 ax + by = d:     

由等式 bx + (a % b) y     

= bx + (a - [a / b] · b) y     

= ay + b(x - [a / b] · y)     

若方程 bx + (a % b) y = d 有解 (x1, y1),     

则方程 ay + b(x - [a / b] · y) = d 也有解 (x1, y1),   

则方程 ax + by = d 有解 (y1, x1 - [a / b] · y1)。

算法:对于方程 ax + by = d,先解方程 bx + (a % b) y = d,再将 (x1, y1) 转化为 (y1, x1 - [a / b] · y1),递归求解即可。

LL exgcd(LL a, LL b, LL &x, LL &y) {
    if(b == 0) {
        x = 1;
        y = 0;
        return a;
    }
    else {
        LL d = exgcd(b, a % b, x, y);
        y = x - a / b * y;
        x = y;
        return d;
    }
}

另一种写法(比较常用):

LL exgcd(LL a, LL b, LL &x, LL &y) {
    if(b == 0) {
        x = 1;
        y = 0;
        return a;
    }
    else {
        LL d = exgcd(b, a % b, y, x);
        y -= a / b * x;
        return d;
    }
}

利用扩展欧几里德算法求丢番图方程 ax + by = C 符合特定条件的解的步骤:     

①LL d = exgcd(a, b, x, y),     

如果 C % d != 0 则无解,     

如果 C % d == 0,

则此时的 (x, y) 为方程 ax + by = d 的某一组特解。     

②将上述解乘以 (C / d),得到对应原方程的解,但该解的数值范围是不定的。     

③通解公式 x = x0 + (b / d)t,y = y0 - (a / d)t,其中 t ∈ Z     

while(?) {x -= b / d; y += a / d; }     

while(?) {x += b / d; y -= a / d; }

6.逆元

定理就不写了,逆元就是求(a/b)%p,求出b的逆元,就可以转化为 (a*inv(b))%p

1)扩展欧几里德算法求乘法逆元

设 a 关于模 p 乘法的逆元为 x,得 ax % p = 1,这个式子可以写成 ax = kp + 1,亦即 ax + pk = 1, k ∈ Z。

LL inv(LL a, LL p) {
    LL x, k;
    LL d = exgcd(a, p, x, k);
    if(d == 1) return (x % p + p) % p;
    else return -1;
}

2)递推法求乘法逆元

考虑关于模 p 乘法,a 的逆元和 (p % a) 的逆元的关系

结论:inv(a) = inv(p % a) ×p (p - [p / a])     

证明:易知 p % a = p - [p / a] × a     两边同取模 p,得  p % a = (p - [p / a] × a) % p   

 同乘 inv(p % a),得 1 = inv(p % a) × (p - [p / a] × a) % p     

同乘 inv(a),得 inv(a) = inv(p % a) × (p - [p / a]) % p

时间复杂度:O(log p)  要求:a < p 且 a 与 p 互质

LL inv(LL a, LL p){
    if(a == 1) return 1;
    return inv(p % a, p) * (p - p / a) % p;
}

递推法求 [1, n] 内所有数的模 p 乘法逆元

时间复杂度:O(n) 打表,O(1) 查询

LL inv[MAXN];
void getinv() {
    inv[1] = 1;
    for(int i = 2;i < MAXN; i++) {
        inv[i] = (p - p / i) * inv[p % i] % p;
    }
}

3)费马小定理求乘法逆元

费马小定理:设 p 是一个素数,a 是一个与 p 互素的正整数,则 ap-1 ≡ 1 (mod p)。

将 ap-1 % p = 1 两边同乘 inv(a) ,得 inv(a) = ap-2 % p

一般 a < 109,p = 1000000007,符合定理条件,但传统的求幂方法显然速度很慢.

LL inv(LL a, LL p) {
    return pow_mod(a, p-2, p);
}

7.快速幂

快速幂在前面也有写过,这里直接提供上面代码中%p意义下的快速幂

LL pow_mod(LL a, LL n, LL p){
    LL ret = 1;
    LL tmp = a;
    while(n) {
        if(n & 1) ret = (ret * tmp) % p;
        tmp = tmp * tmp % p;
        n >>= 1;
    }
    return ret;
}

矩阵快速幂

typedef long long LL;
const int SIZE=20;
const LL MOD=1000000007;

struct Matrix {
    int r, c;
    LL mat[SIZE][SIZE];
    Matrix() {}
    Matrix(int _r, int _c) {
        r = _r;
        c = _c;
        memset(mat, 0, sizeof(mat));
    }
};

Matrix operator + (Matrix a, Matrix b) {
    Matrix s(a.r, a.c);
    for(int i = 0; i < a.r; i++) {
        for(int j = 0; j < a.c; j++) {
            s.mat[i][j] = (a.mat[i][j] + b.mat[i][j]) % MOD;
        }
    }
    return s;
}

Matrix operator * (Matrix a, Matrix b) {
    Matrix s(a.r, b.c);
    for(int i = 0; i < a.r; i++) {
        for(int k = 0; k < a.c; k++) { //j, k交换顺序运行更快
            for(int j = 0; j < b.c; j++) {
                s.mat[i][j] = (s.mat[i][j] + a.mat[i][k] * b.mat[k][j]) % MOD;
            }
        }
    }
    return s;
}

Matrix pow_mod(Matrix a, LL n) {
    Matrix ret(a.r, a.c);
    for(int i = 0; i < a.r; i++) {
        ret.mat[i][i]=1; //单位矩阵
    }
    Matrix tmp(a);
    while(n) {
        if(n&1) ret = ret * tmp;
        tmp = tmp * tmp;
        n >>= 1;
    }
    return ret;
}

 

 

 

 

 

 

 

猜你喜欢

转载自blog.csdn.net/qq_41383801/article/details/81293346
今日推荐