当前位置: 首页 > news >正文

【简单数论】(模运算,快速幂,乘法逆元,同余,exgcd,gcd,欧拉函数,质数,欧拉筛,埃式筛,调和级数枚举,约数,组合数)

数论

模运算

  • a   m o d   b = a − ⌊ a / b ⌋ × b a\ mod \ b = a - \lfloor a / b \rfloor \times b a mod b=aa/b×b

  • n   m o d   p n \ mod\ p n mod p得到的结果的正负至于被除数 n n n有关

image-20250404133801773

模运算的性质:

( a + b )   m o d   m = ( ( a   m o d   m ) + ( b   m o d   m ) )   m o d   m (a + b)\ mod\ m = ((a\ mod\ m) + (b\ mod\ m))\ mod\ m (a+b) mod m=((a mod m)+(b mod m)) mod m

( a − b )   m o d   m = ( ( a   m o d   m ) − ( b   m o d   m ) )   m o d   m (a - b)\ mod\ m = ((a\ mod\ m) - (b\ mod\ m))\ mod\ m (ab) mod m=((a mod m)(b mod m)) mod m = ( ( a   m o d   m ) − ( b   m o d   m ) + m )   m o d   m ((a\ mod\ m) - (b\ mod\ m) + m)\ mod\ m ((a mod m)(b mod m)+m) mod m

( a × b )   m o d   m = ( ( a   m o d   m ) × ( b   m o d   m ) )   m o d   m (a \times b)\ mod\ m = ((a\ mod\ m) \times (b\ mod\ m))\ mod\ m (a×b) mod m=((a mod m)×(b mod m)) mod m

但是除法例外,除法的取模需要用到逆元。

计算减法的时候,通常需要加上模数,防止出现负数。

快速幂

快速求解 a b   m o d   p a^b \ mod \ p ab mod p,可以采用二进制拼凑的思想

对于 b b b,它可以看成一个二进制数,可以写成 2 2 2的幂相加的形式,那么 a b a^b ab可以写成 a ( 2 i + 2 j + . . . + 2 k ) = a 2 i × a 2 j × . . . × a 2 k   ( i , j , k ∈ S , 其中集合 S 为 b 转换成二进制后位置上是 1 的位置 ) a^{(2^{i} + 2^j + ... + 2^k)} = a^{2^i}\times a^{2^j} \times ... \times a^{2^k} \ (i, j, k \in S,其中集合S为b转换成二进制后位置上是1的位置) a(2i+2j+...+2k)=a2i×a2j×...×a2k (i,j,kS,其中集合Sb转换成二进制后位置上是1的位置)

我们只需计算的同时预处理每一项 a 2 i a^{2^i} a2i即可

using ll = long long;
ll qmi(ll a, ll b, ll c){
    ll res = 1;
    while(b){
        if(b & 1 == 1) res = res * a % c;
        a = a * a % c;
        b >>= 1;
    }
    return res;
}

乘法逆元

a × x ≡ 1   ( m o d   b ) a \times x \equiv 1 \ (mod \ b) a×x1 (mod b),且 a a a b b b互质,我们定义 x x x a a a的逆元,记为 a − 1 a^{-1} a1, x x x可称为 a a a在模 b b b意义下的倒数

对于 a b   m o d   p \frac{a}{b} \ mod \ p ba mod p,我们可以求出 b b b m o d   p mod \ p mod p意义下的逆元,然后乘上 a a a,再 m o d   p mod \ p mod p即可

注意对于数 a a a在模 p p p意义下的乘法逆元,我们需要确保 a a a p p p互质,此时才有 a a a的乘法逆元 a − 1 a^{-1} a1,此条件等价于 p p p是质数

费马小定理

a p − 1 ≡ 1   ( m o d   p ) a^{p - 1} \equiv 1 \ (mod \ p) ap11 (mod p),其中 p p p是素数

费马小定理求逆元
a × a p − 2 ≡ 1   ( m o d   p ) a \times a^{p - 2} \equiv 1 \ (mod\ p) a×ap21 (mod p),根据乘法逆元的定义可知,此时 a a a的逆元是 a p − 2 a^{p - 2} ap2

ll inv(ll a, ll p){
    return qmi(a, p - 2, p);
}

扩展欧几里得求逆元

根据逆元的定义 a x ≡ 1   ( m o d   p ) ax \equiv 1 \ (mod \ p) ax1 (mod p),我们得到方程: a x − 1 = y p ax - 1 = yp ax1=yp a x − 1 ax - 1 ax1 p p p的倍数),则 a x + p y = 1 ax + py = 1 ax+py=1

解方程得 x   m o d   p x \ mod \ p x mod p得到的就是 a a a的乘法逆元,同时逆元存在的条件是 g c d ( a , p ) = 1 gcd(a, p) = 1 gcd(a,p)=1

求阶乘的乘法逆元

同余

两个整数 a a a b b b对用一个正整数 m m m取模后余数相同,则称 a a a b b b对模 m m m同余,记作 a ≡ b   ( m o d   m ) a \equiv b \ (mod\ m) ab (mod m),这意味着 m   ∣   ( a − b ) m\ | \ (a - b) m  (ab)

同余的性质:

  • 自反性: a ≡ a   ( m o d   m ) a \equiv a \ (mod\ m) aa (mod m)
  • 对称性:若 a ≡ b   ( m o d   m ) a \equiv b \ (mod \ m) ab (mod m),则 b ≡ a   ( m o d   m ) b \equiv a \ (mod \ m) ba (mod m)
  • 传递性:若 a ≡ b   ( m o d   m ) , b ≡ c   ( m o d   m ) a \equiv b \ (mod\ m), b \equiv c \ (mod\ m) ab (mod m),bc (mod m),则 a ≡ c   ( m o d   m ) a \equiv c \ (mod\ m) ac (mod m)
  • 同加性:若 a ≡ b   ( m o d   m ) a \equiv b \ (mod\ m) ab (mod m),则 a ± c ≡ b ± c   ( m o d   m ) a \pm c \equiv b \pm c \ (mod\ m) a±cb±c (mod m)
  • 同乘性:若 a ≡ b   ( m o d   m ) a \equiv b \ (mod\ m) ab (mod m),则 a × c ≡ b × c   ( m o d   m ) a \times c \equiv b \times c \ (mod\ m) a×cb×c (mod m),若 a ≡ b   ( m o d   m ) , c ≡ d   ( m o d   m ) a \equiv b \ (mod \ m), c \equiv d \ (mod \ m) ab (mod m),cd (mod m),则 a × c ≡ b × d   ( m o d   m ) a \times c \equiv b \times d \ (mod\ m) a×cb×d (mod m)
  • 同幂性:若 a ≡ b   ( m o d   m ) a \equiv b \ (mod \ m) ab (mod m),则 a c ≡ b c   ( m o d   m ) a^c \equiv b^c \ (mod\ m) acbc (mod m)
  • 不满足同除性,但是有,若 c a ≡ c b   ( m o d   m ) ca \equiv cb \ (mod \ m) cacb (mod m),则 a ≡ b   (   m o d   m g c d ( m , c ) ) a \equiv b \ (\ mod \ \frac{m}{gcd(m, c)}) ab ( mod gcd(m,c)m)

扩展欧几里得定理

对于给定的两个整数 a a a b b b,必须存在整数 x x x y y y,使得 a × x + b × y = g c d ( a , b ) a \times x + b \times y = gcd(a, b) a×x+b×y=gcd(a,b)

裴蜀定理

方程 a × x + b × y = c a \times x + b \times y = c a×x+b×y=c有整数解的充分必要条件是 c c c g c d ( a , b ) gcd(a, b) gcd(a,b)的倍数

证明:

  • 必要性:令 p = g c d ( a , b ) p = gcd(a, b) p=gcd(a,b),则有 a = p × a ′ a = p \times a' a=p×a b = p × b ′ b = p \times b' b=p×b;则 c = p × ( a ′ x + b ′ y ) c = p \times (a'x + b'y) c=p×(ax+by),即 g c d ( a , b ) gcd(a, b) gcd(a,b)的倍数。

  • 充分性:考虑 g c d gcd gcd的欧几里得算法。 a , b a, b a,b通过 b , a % b b, a \% b b,a%b的方式一直辗转下去,最后会出现 p , 0 p, 0 p,0的形式。原因是 0 ≤ a % b ≤ b − 1 0 \leq a \% b \leq b - 1 0a%bb1。对于递归出口 p , 0 p, 0 p,0,方程 a × p + 0 × y = c a \times p + 0 \times y = c a×p+0×y=c是否存在解呢?显然有解,由于充分性,这里 a a a c p \frac{c}{p} pc即可。那么辗转相除过程中,下面的成立能否推得上面的成立呢?

    • 对于方程 b x 1 + a % b × y 1 = c = a x + b y bx_1 + a \% b \times y_1 = c = ax + by bx1+a%b×y1=c=ax+by

    • 左边 = b x 1 + ( a − ⌊ a b ⌋ × b ) × y 1 = a y 1 + b x 1 − ⌊ a b ⌋ × b y 1 = a y 1 + b × ( x 1 − ⌊ a b ⌋ × y 1 ) = 右边 = a x + b y 左边 = bx_1 + (a - \lfloor \frac{a}{b} \rfloor \times b) \times y_1 = ay_1 + bx_1 - \lfloor \frac{a}{b} \rfloor \times by_1 = ay_1 + b \times (x_1 - \lfloor \frac{a}{b} \rfloor \times y_1) = 右边 = ax + by 左边=bx1+(aba×b)×y1=ay1+bx1ba×by1=ay1+b×(x1ba×y1)=右边=ax+by

      所以找到一组特解: x = y 1 x = y_1 x=y1 y = x 1 − ⌊ a b ⌋ × y 1 y = x_1 - \lfloor \frac{a}{b} \rfloor \times y_1 y=x1ba×y1,就可以通过最下面的特解推得所有的特解。

    • 对于特解 ( x 0 , y 0 ) (x_0, y_0) (x0,y0),设下一组解是 ( x 0 + d 1 , y 0 + d 2 ) (x_0 + d_1, y_0 + d_2) (x0+d1,y0+d2)

      a × ( x 0 + d 1 ) + b × ( y 0 + d 2 ) = c a \times (x_0 + d_1) + b \times (y_0 + d_2) = c a×(x0+d1)+b×(y0+d2)=c,又, a × x 0 + b × y 0 = c a \times x_0 + b \times y_0 = c a×x0+b×y0=c,则 a d 1 + b d 2 = 0 ad_1 + bd_2 = 0 ad1+bd2=0

      故, d 1 d 2 = − b a = − b / g c d ( a , b ) a / g c d ( a , b ) \frac{d_1}{d_2} = -\frac{b}{a} = -\frac{b / gcd(a, b)}{a / gcd(a, b)} d2d1=ab=a/gcd(a,b)b/gcd(a,b)

      d 1 = b g c d ( a , b ) d_1 = \frac{b}{gcd(a, b)} d1=gcd(a,b)b d 2 = − a g c d ( a , b ) d_2 = -\frac{a}{gcd(a, b)} d2=gcd(a,b)a,故一般解为:

      x = x 0 + k × b g c d ( a , b ) x = x_0 + k \times \frac{b}{gcd(a, b)} x=x0+k×gcd(a,b)b y = y 0 − k × a g c d ( a , b )   ( k ∈ Z ) y = y_0 - k \times \frac{a}{gcd(a, b)} \ (k \in Z) y=y0k×gcd(a,b)a (kZ)

    • 若要求 x x x的最小整数解,则可以通过 ( x 0   m o d   ( b g c d ( a , b ) ) + b g c d ( a , b ) )   m o d   x 0 (x_0 \ mod \ (\frac{b}{gcd(a, b)}) + \frac{b}{gcd(a, b)}) \ mod \ x_0 (x0 mod (gcd(a,b)b)+gcd(a,b)b) mod x0,原因是 x 0 x_0 x0的周期是 b g c d ( a , b ) \frac{b}{gcd(a, b)} gcd(a,b)b,所以可通过取模的方式得到结果

扩展欧几里得算法

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

GCD

最大公约数:欧几里得算法,时间复杂度 O ( l o g   a ) O(log\ a) O(log a)

g c d ( a , b ) = g c d ( b , a   m o d   b ) gcd(a, b) = gcd(b, a\ mod\ b) gcd(a,b)=gcd(b,a mod b)

证明如下:

  • p = g c d ( a , b ) p = gcd(a, b) p=gcd(a,b),则有 a = p × a ′ a = p \times a' a=p×a b = p × b ′ b = p \times b' b=p×b g c d ( a ′ , b ′ ) = 1 gcd(a', b') = 1 gcd(a,b)=1

    • a   m o d   b = a − ⌊ a / b ⌋ × b = p × a ′ − p × b ′ × ⌊ a / b ⌋ a\ mod\ b = a - \lfloor a / b \rfloor \times b = p \times a' - p \times b' \times \lfloor a / b \rfloor a mod b=aa/b×b=p×ap×b×a/b

    • 可以看出, p   ∣   b p \ | \ b p  b p   ∣   ( a   m o d   b ) p \ | \ (a \ mod \ b) p  (a mod b)

  • 那么 p p p是否是最大公约数呢?即我们需要证明 g c d ( b ′ , a ′ − b ′ × ⌊ a / b ⌋ ) = 1 gcd(b', a' - b' \times \lfloor a / b \rfloor) = 1 gcd(b,ab×a/b⌋)=1

    • g c d ( b ′ , a ′ − b ′ × ⌊ a / b ⌋ ) = d gcd(b', a' - b' \times \lfloor a / b \rfloor) = d gcd(b,ab×a/b⌋)=d,则有 b ′ = d × b ′ ′ b' = d \times b'' b=d×b′′ a ′ − b ′ × ⌊ a / b ⌋ = d × c a' - b' \times \lfloor a / b \rfloor = d \times c ab×a/b=d×c
    • 转换得到 a ′ = d × c + b ′ × ⌊ a / b ⌋ = d × c + d × b ′ ′ × ⌊ a / b ⌋ a' = d \times c + b' \times \lfloor a / b \rfloor = d \times c + d \times b'' \times \lfloor a / b \rfloor a=d×c+b×a/b=d×c+d×b′′×a/b
    • 可以发现 d   ∣   b ′ d\ |\ b' d  b d   ∣   a ′ d\ | \ a' d  a,与上述 g c d ( a ′ , b ′ ) = 1 gcd(a', b') = 1 gcd(a,b)=1矛盾,则命题得证。

欧拉函数

1... N 1...N 1...N中与 N N N互质的数的个数,被称为欧拉函数,记作 ϕ ( N ) \phi(N) ϕ(N)

在唯一分解定理中, N = p 1 a 1 × p 2 a 2 × . . . × p m a m N = p_1^{a_1} \times p_2^{a_2} \times ...\times p_m^{a_m} N=p1a1×p2a2×...×pmam,则 ϕ ( N ) = N × p 1 − 1 p 1 × p 2 − 1 p 2 × . . . × p m − 1 p m = N × ( 1 − 1 p 1 ) × ( 1 − 1 p 2 ) × . . . × ( 1 − 1 p 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} = N \times (1 - \frac{1}{p_1}) \times (1 - \frac{1}{p_2}) \times...\times (1 - \frac{1}{p_m}) ϕ(N)=N×p1p11×p2p21×...×pmpm1=N×(1p11)×(1p21)×...×(1pm1)

证明:

先对 N N N分解质因数,得到 N = p 1 a 1 × p 2 a 2 × . . . × p m a m N = p_1^{a_1} \times p_2^{a_2} \times ...\times p_m^{a_m} N=p1a1×p2a2×...×pmam

[ 1 , N ] [1, N] [1,N]中,我们先将所以 p 1 , p 2 , . . . , p m p_1, p_2, ..., p_m p1,p2,...,pm的倍数减掉,再将所有 p i × p j p_i \times p_j pi×pj的倍数的数加上,再减去 p i × p j × p k p_i \times p_j \times p_k pi×pj×pk的倍数的数,以此类推。最后剩下的就是与 N N N互质的数的个数。式子表达为 N − ( N p 1 + N p 2 + . . . + N p m ) + ( N p 1 p 2 + N p 1 p 3 + . . . ) − ( N p 1 p 2 p 3 + . . . ) . . . N - (\frac{N}{p_1} + \frac{N}{p_2} + ... + \frac{N}{p_m}) + (\frac{N}{p_1p_2} + \frac{N}{p_1p_3} + ... ) - (\frac{N}{p_1p_2p_3} + ...) ... N(p1N+p2N+...+pmN)+(p1p2N+p1p3N+...)(p1p2p3N+...)...

该式就是 N × ( 1 − 1 p 1 ) × ( 1 − 1 p 2 ) × . . . × ( 1 − 1 p m ) N \times (1 - \frac{1}{p_1}) \times (1 - \frac{1}{p_2}) \times...\times (1 - \frac{1}{p_m}) N×(1p11)×(1p21)×...×(1pm1)的展开式

公式法求欧拉函数

#include <bits/stdc++.h>
using namespace std;

void solve() {
    int n;
    cin >> n;
    auto f = [&](int x) -> int {
        int res = x;
        for (int i = 2; i <= x / i; i ++) {
            if (x % i != 0) continue;
            res = res / i * (i - 1);
            while (x % i == 0) x /= i;
        }  
        if (x > 1) res = res / x * (x - 1); 
        return res;
    };
    cout << f(n) << endl;
}

int main() {
    int t;
    cin >> t;
    while (t --) {
        solve();
    }
    return 0;
}

筛法求欧拉函数

对于一个质数 p p p来说, ϕ ( p ) = p − 1 \phi(p) = p - 1 ϕ(p)=p1

对于每一个数 p r i m e s [ j ] × i primes[j] \times i primes[j]×i来说

  • i % p r i m e s [ j ] = 0 i \% primes[j] = 0 i%primes[j]=0时, i i i中的质因子包括 p r i m e s [ j ] primes[j] primes[j],则有 ϕ ( p r i m e s [ j ] × i ) = ϕ ( i ) × p r i m e s [ j ] \phi(primes[j] \times i) = \phi(i) \times primes[j] ϕ(primes[j]×i)=ϕ(i)×primes[j]
  • i % p r i m e s [ j ] ≠ 0 i \% primes[j] \neq 0 i%primes[j]=0时, i × p r i m e s [ j ] i \times primes[j] i×primes[j]中的质因子既包括 i i i中的质因子,又包括 p r i m e s [ j ] primes[j] primes[j],则有 ϕ ( p r i m e s [ j ] × i ) = ϕ ( i ) × p r i m e s [ j ] × ( 1 − 1 p r i m e s [ j ] ) \phi(primes[j] \times i) = \phi(i) \times primes[j] \times (1 - \frac{1}{primes[j]}) ϕ(primes[j]×i)=ϕ(i)×primes[j]×(1primes[j]1)
#include <bits/stdc++.h>s
using namespace std;

#define int long long

const int N = 1000010;
int cnt, primes[N];
bool st[N];
int phi[N];

int getEuler(int n) {
    phi[1] = 1;
    for (int i = 2; i <= n; i ++) {
        if (st[i] == false) {
            primes[cnt ++] = i;
            phi[i] = i - 1;
        }
        for (int j = 0; j < cnt && primes[j] <= n / i; j ++) {
            st[primes[j] * i] = true;
            if (i % primes[j] == 0) {
                phi[i * primes[j]] = phi[i] * primes[j];
                break;
            }
            phi[i * primes[j]] = phi[i] * primes[j] / primes[j] * (primes[j] - 1);
        }
    }
    int res = 0;
    for (int i = 1; i <= n; i ++) {
        res += phi[i];
    }
    return res;
}

signed main() {
    int n;
    cin >> n;
    cout << getEuler(n) << endl;
    return 0;
}

质数

分解质因数

给定一个数 x x x,由唯一分解定理可知, x = p 1 a 1 × p 2 a 2 × p 3 a 3 . . .   ( p 1 < p 2 < p 3 ) x = p_1^{a_1} \times p_2^{a_2} \times p_3^{a_3}...\ (p_1 < p_2 < p_3) x=p1a1×p2a2×p3a3... (p1<p2<p3),我们从小到大枚举每一个数,最先整除 x x x的除数 n n n一定是素数。同时将这个数中所有 n n n的因子除干净,那么下次 x x x被整除时除数也是素数,从而分解了质因数。

#include <bits/stdc++.h>
using namespace std;

int main() {
    int n;
    cin >> n;
    for (int i = 0; i < n; i ++) {
        int x;
        cin >> x;
        map<int, int> cnt;
        vector<int> res;
        for (int j = 2; j <= x / j; j ++) {
            if (x % j != 0) continue;
            res.push_back(j);
            while (x % j == 0) {
                cnt[j] ++;
                x /= j;
            }
        }
        if (x > 1) {
            res.push_back(x);
            cnt[x] ++;
        }
        for (auto& u : res) {
            cout << u << " " << cnt[u] << endl;
        }
        cout << endl;
    }
    return 0;
}

普通筛法

给定一个整数 x x x,并筛掉所有倍数,如 2 x , 3 x , 4 x . . . 2x, 3x, 4x... 2x,3x,4x...

筛完后, v i s [ i ] = f a l s e vis[i] = false vis[i]=false的为素数

这种方法的时间复杂度是 O ( n   l o g   n ) O(n\ log\ n) O(n log n)级别

对于每一个外层循环变量 i i i,内层循环枚举所有不超过 n n n i i i的倍数,枚举次数是 ⌊ n i ⌋ \lfloor \frac{n}{i} \rfloor in,又 ⌊ n i ⌋ < n i \lfloor \frac{n}{i} \rfloor < \frac{n}{i} in<in,有调和级数的渐近公式 ∑ i = 1 n 1 i ≈ l n ( n + 1 ) + 0.577218 \sum^{n}_{i = 1} \frac{1}{i} \approx ln(n + 1) + 0.577218 i=1ni1ln(n+1)+0.577218,其中 0.577218 0.577218 0.577218是欧拉常数

所以复杂度是 O ( n   l o g   n ) O(n\ log\ n) O(n log n)级别

虽然这个筛法的时间复杂度很差,但是调和级数枚举引人深思,这种枚举方式非常巧妙,而且复杂度仅有 O ( l o g   n ) O(log\ n) O(log n)级别

埃式筛法

在调和级数枚举的筛法中,我们发现枚举4的倍数无意义,因为从这里筛掉的数一定从2的倍数里筛掉了,所以我们想到:只用质数去筛

其复杂度只有 O ( n   l o g   l o g   n ) O(n\ log\ log\ n) O(n log log n)

#include <bits/stdc++.h>
using namespace std;

const int N = 1e6 + 10;
int cnt, primes[N], n;
bool st[N];

int main() {
    cin >> n;
    for (int i = 2; i <= n; i ++) {
        if (st[i]) continue;
        primes[cnt ++] = i;
        for (int j = 2 * i; j <= n; j += i) {
            st[j] = true;
        }
    }
    cout << cnt << endl;
    return 0;
}

欧拉筛

通过对埃氏筛法的分析,我们发现 6 = 2 × 3 6 = 2 \times 3 6=2×3会被质数 2 2 2 3 3 3各筛一次。一个数有多少个质因子,它就会被筛多少次。那么我们能否让一个数只被筛掉一次呢?

欧拉筛法可以做到并且每个数会被它的最小质因子筛掉

#include <bits/stdc++.h>
using namespace std;

const int N = 1e6 + 10;
int cnt, primes[N], n;
bool st[N];

int main() {
    cin >> n;
    for (int i = 2; i <= n; i ++) {
        if (!st[i]) primes[cnt ++] = i; //是素数
        for (int j = 0; j < cnt && primes[j] <= n / i; j ++) {
            st[primes[j] * i] = true;
            if (i % primes[j] == 0) break;
        }
    }
    cout << cnt << endl;
    return 0;
}
  • x = i × p r i m e s [ j ] x = i \times primes[j] x=i×primes[j]
  • i   m o d   p r i m e s [ j ] ≠ 0 i \ mod \ primes[j] \neq 0 i mod primes[j]=0,则 i i i中没有 p r i m e s [ j ] primes[j] primes[j]这个质因子。又因为 j j j是从小到大遍历的,所以 p r i m e s [ j ] < i primes[j] < i primes[j]<i,则 p r i m e s [ j ] primes[j] primes[j] x x x的最小质因子
  • i   m o d   p r i m e s [ j ] = 0 i \ mod \ primes[j] = 0 i mod primes[j]=0 ,说明 p r i m e s [ j ] primes[j] primes[j] i i i的一个质因子。又因为 j j j是从小到大遍历的,所以 p r i m e s [ j ] primes[j] primes[j] i i i的最小质因子。所以 p r i m e s [ j ] primes[j] primes[j] x x x的最小质因子
  • 综上所述,每个数都是被其最小质因子 p r i m e s [ j ] primes[j] primes[j]筛掉且只会被筛掉一次

调和级数枚举模型

内容参考于:[OI&ACM]调和级数枚举倍数模型 - 知乎
给定一个数 n   ( n ≤ 1 0 6 ) n\ (n \leq 10^6) n (n106),求 [ 1 , n ] [1, n] [1,n]中所有整数 i i i的正因子个数

思路:枚举 [ 1 , n ] [1, n] [1,n]中的所有数 i i i,对 i i i的所有倍数, i i i 会增加 1 1 1的贡献。考虑调和级数枚举:

int d[1000001] = {0}; // 储存因数倍数,初始全为 0
for (int i = 1; i <= n; i++) // 枚举 [1, n] 的数
{
    for (int j = i; j <= n; j += i) // 枚举 i 的倍数
    {
        d[j]++;
    }
}

时间复杂度为 O ( n   l o g   n ) O(n \ log\ n) O(n log n)级别

下面的题目都应用了该模型,读者不妨观看思考

题目1:Problem - D - Codeforces

大意:给定一个长度为 n   ( n ≤ 1 0 5 ) n\ (n \leq 10^5) n (n105)的数组,每次操作可以任取一些位置上的数,并将该位置上的数染成黑色。然后将这些被染成黑色的位置索引的所有倍数染成绿色,每次操作的得分是该数组中黑色和绿色位置上的数的最大值。由于选择染成黑色的操作有 2 n − 1 2^n - 1 2n1种,所以我们要求出所有操作的得分的和

选择每个数所能得到的最大值是容易处理的。

vector<int> maxv(n + 1, 0);
for (int i = 1; i <= n; i ++) {
    for (int j = i; j <= n; j += i) {
        maxv[i] = max(maxv[i], a[j]);
    }
}

我们明确 m a x v maxv maxv数组的含义:选择第 i i i上的数,将其涂成黑色,并将其索引的倍数涂成绿色,所能得到的最大值。假设有这样的 m a x v maxv maxv数组, m a x v = [ 1 , 6 , 7 , 3 ] maxv = [1, 6, 7, 3] maxv=[1,6,7,3] 1 1 1贡献了 1 1 1次, 3 3 3贡献了 2 2 2次, 6 6 6贡献了 4 4 4次, 7 7 7贡献了 8 8 8次。这是因为:对于第一个位置上的数,我们有如下的选择方式 [ 1 ] , [ 1 , 2 ] , [ 1 , 3 ] , [ 1 , 4 ] , [ 1 , 2 , 3 ] , [ 1 , 2 , 4 ] , [ 1 , 3 , 4 ] , [ 1 , 2 , 3 , 4 ]   ( [ 1 , 3 , 4 ] 表示我们选择第 1 , 3 , 4 个数字,其余同理 ) [1], [1, 2], [1, 3], [1, 4], [1, 2, 3],[1, 2, 4], [1, 3, 4], [1, 2, 3, 4]\ ([1, 3, 4]表示我们选择第1,3,4个数字,其余同理) [1],[1,2],[1,3],[1,4],[1,2,3],[1,2,4],[1,3,4],[1,2,3,4] ([1,3,4]表示我们选择第134个数字,其余同理),由于 1 1 1是最小的,在如下选择中只贡献了 1 1 1次,就是选择方式为 [ 1 ] [1] [1]的时候。所以我们可以将 m a x v maxv maxv数组排序并用贡献法求出答案

sort(maxv.begin() + 1, maxv.begin() + n + 1);
LL res = 0, pow2 = 1;	
for (int i = 1; i <= n; i ++) {
    res = (res % mod + maxv[i] * pow2 % mod) % mod;
    pow2 = pow2 * 2 % mod;
}

cout << res << endl;

题目2:Problem - D - Codeforces

大意:给定一个数组 a a a,其长度为 n n n,其中 1 ≤ n ≤ 1 0 6 1 \leq n \leq 10^6 1n106 1 ≤ a i ≤ n 1 \leq a_i \leq n 1ain。若不存在 k , ( 1 ≤ k ≤ n ) k,(1 \leq k \leq n) k(1kn)使得 a k   ∣   a i a_k \ | \ a_i ak  ai a k   ∣   a j a_k \ | \ a_j ak  aj,那么称 ( i , j ) (i, j) (i,j)是对儿好整数。求好整数的数量。

  • 条件 a k   ∣   a i a_k \ | \ a_i ak  ai a k   ∣   a i a_k \ | \ a_i ak  ai可以转化为 a k   ∣   g c d ( a i , a j ) a_k \ | \ gcd(a_i, a_j) ak  gcd(ai,aj)。由于值域仅有 1 0 6 10^6 106级别,我们可以预处理出 f ( x )   ( 1 ≤ x ≤ 1 0 6 ) f(x) \ (1 \leq x \leq 10^6) f(x) (1x106) f ( x ) f(x) f(x)表示 g c d ( i , j ) = x gcd(i, j) = x gcd(i,j)=x好整数的个数。

    • y   ∣   g c d ( a i , a j ) y\ | \ gcd(a_i, a_j) y  gcd(ai,aj),此时 a i a_i ai a j a_j aj y y y的倍数,故可以枚举 y y y的倍数计算出数量 s u m sum sum。那么 y   ∣   g c d ( a i , a j ) y \ | \ gcd(a_i,a_j) y  gcd(ai,aj) ( i , j ) (i, j) (i,j)的对数是 C s u m 2 = s u m × ( s u m − 1 ) 2 C_{sum}^2 = \frac{sum \times (sum - 1)}{2} Csum2=2sum×(sum1)。其中存在两种情况 y = g c d ( a i , a j ) y = gcd(a_i, a_j) y=gcd(ai,aj) y < g c d ( a i , a j ) y < gcd(a_i, a_j) y<gcd(ai,aj),(也就是 g c d ( a i , a j ) gcd(a_i, a_j) gcd(ai,aj) y y y的大于 1 1 1倍的倍数)。所以 f ( x ) = s u m × ( s u m − 1 ) 2 − f ( 2 x ) − f ( 3 x ) − . . . f(x) = \frac{sum \times (sum - 1)}{2} - f(2x) - f(3x) -... f(x)=2sum×(sum1)f(2x)f(3x)...
    for (int i = n; i >= 1; i --) {
        int s = 0, t = 0;
        for (int j = i; j <= n; j += i) {
            s += cnt[j];
            t += f[j];
        }
        f[i] = s * (s - 1) / 2 - t;
    
    }
    
  • 在值域之中,我们将 a m ( 1 ≤ m ≤ n ) a_m(1 \leq m \leq n) am(1mn)及其倍数筛掉,剩下的就是 f ( x ) f(x) f(x) x x x可以取的值。于是 a n s = ∑ i = 1 n f ( i )   ( 其中 i 未被筛掉 ) ans = \sum^{n}_{i = 1}f(i) \ (其中i未被筛掉) ans=i=1nf(i) (其中i未被筛掉)

#include <bits/stdc++.h>
using namespace std;

using LL = long long;

#define int LL

void solve() {
	int n;
	cin >> n;
	vector<int> f(n + 1, 0);
	vector<int> cnt(n + 1, 0);
	for (int i = 0; i < n; i ++) {
		int x;
		cin >> x;
		cnt[x] ++;
	}
	
	for (int i = n; i >= 1; i --) {
		int s = 0, t = 0;
		for (int j = i; j <= n; j += i) {
			s += cnt[j];
			t += f[j];
		}
		f[i] = s * (s - 1) / 2 - t;
		
	}
	
	vector<int> vis(n + 1, false);
	for (int i = 1; i <= n; i ++) {
		if (cnt[i]) {
			for (int j = i; j <= n; j += i) {
				vis[j] = true;
			}
		}
	}
	
	int res = 0;
	for (int i = 1; i <= n; i ++) {
		if (vis[i] == false) {
			res += f[i];
		}
	}
	cout << res << endl;
}

signed main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	
	int t;
	cin >> t;
	while (t --) {
		solve();
	}
	
	return 0;
}

约数

约数个数

对于一个正整数 x x x,由于唯一分解定理, x = p 1 a 1 × p 2 a 2 × p 3 a 3 × p 4 a 4 . . . x = p_1^{a_1} \times p_2^{a_2} \times p_3^{a_3} \times p_4^{a_4}... x=p1a1×p2a2×p3a3×p4a4...

对于 x x x的每一个约数 y y y y y y都能写成 y = p 1 b 1 × p 2 b 2 × p 3 b 3 × p 4 b 4 . . .   ( 0 ≤ b 1 ≤ a 1 , 0 ≤ b 2 ≤ a 2 , 0 ≤ b 3 ≤ a 3 , 0 ≤ b 4 ≤ a 4 ) y = p_1^{b_1} \times p_2^{b_2} \times p_3^{b_3} \times p_4^{b_4}... \ (0 \leq b_1 \leq a_1,0 \leq b_2 \leq a_2, 0 \leq b_3\leq a_3,0 \leq b_4 \leq a_4) y=p1b1×p2b2×p3b3×p4b4... (0b1a1,0b2a2,0b3a3,0b4a4)

所以约数个数为 ( a 1 + 1 ) × ( a 2 + 1 ) × ( a 3 + 1 ) × ( a 4 + 1 ) × . . . (a_1 + 1) \times (a_2 + 1) \times (a_3 + 1) \times (a_4 + 1) \times ... (a1+1)×(a2+1)×(a3+1)×(a4+1)×...

约数之和

约数之和 s u m = ( p 1 0 + p 1 1 + . . . + p 1 a 1 ) × ( p 2 0 + p 2 1 + . . . + p 2 a 2 ) × ( p 3 0 + p 3 1 + . . . + p 3 a 3 ) × ( p 4 0 + p 4 1 + . . . + p 4 a 4 ) × . . . sum = (p_1^0 + p_1^1 + ... + p_1^{a_1}) \times (p_2^0 + p_2^1 + ... + p_2^{a_2}) \times (p_3^0 + p_3^1 + ... + p_3 ^ {a_3}) \times (p_4^0 + p_4^1 + ... + p_4^{a_4}) \times ... sum=(p10+p11+...+p1a1)×(p20+p21+...+p2a2)×(p30+p31+...+p3a3)×(p40+p41+...+p4a4)×...

由分配律将上述括号拆开可得: x 1 + x 2 + x 3 + . . . + x_1 + x_2 + x_3 + ... + x1+x2+x3+...+,其中每一项都是 x x x的约数

#include <bits/stdc++.h>
using namespace std;

using LL = long long;

const int mod = 1e9 + 7;

int n;
map<int, int> cnt;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    cin >> n;
    for (int i = 0; i < n; i ++) {
        int x;
        cin >> x;
        for (int j = 2; j <= x / j; j ++) {
            if (x % j != 0) continue;
            int t = 0;
            while (x % j == 0) {
                t ++;
                x /= j;
            }
            cnt[j] += t;
        }
        if (x > 1) cnt[x] ++;
    }   
    LL res = 1;
    for (auto& [u, v] : cnt) {
        LL t = 0;
        for (int j = 0; j <= v; j ++) {
            t = (t * u % mod + 1) % mod;
        }
        res = res * t % mod;
    }
    cout << res << endl;
    return 0;
}

组合数

C n m = C n − 1 m − 1 + C n − 1 m C^{m}_{n} = C_{n - 1}^{m - 1} + C_{n - 1}^{m} Cnm=Cn1m1+Cn1m

可由此式子递推计算组合数

void init() {
    for (int i = 0; i <= N - 1; i ++) {
        c[i][0] = 1;
    }
    for (int i = 1; i <= N - 1; i ++) {
        for (int j = 1; j <= N - 1; j ++) {
            c[i][j] = (c[i - 1][j] % mod + c[i - 1][j - 1] % mod) % mod;
        }
    }
}

C n m = A n m A m m = n ! ( n − m ) ! m ! = n ! m ! ( n − m ) ! C_n^m = \frac{A_n^m}{A_m^m} = \frac{\frac{n!}{(n - m)!}}{m!} = \frac{n!}{m!(n - m)!} Cnm=AmmAnm=m!(nm)!n!=m!(nm)!n!

可以预处理出来 f a c t [ N ] fact[N] fact[N] i n f a c t [ N ] infact[N] infact[N]

C n m = f a c t [ N ] × i n f a c t [ m ] × i n f a c t [ n − m ] C_n^m = fact[N] \times infact[m] \times infact[n - m] Cnm=fact[N]×infact[m]×infact[nm]

#include <bits/stdc++.h>
using namespace std;
using LL = long long;
#define endl "\n"
typedef pair<int, int> pII;
const int p = 1e9 + 7;
const int N = 100010;
LL fact[N], infact[N];

LL qmi(LL a, LL b, LL p) {
	LL res = 1;
	while (b) {
		if (b & 1) res = res * a % p;
		a = a * a % p;
		b >>= 1;
	}
	return res;
}

LL inv(LL x) {return qmi(x, p - 2, p);}

void init(int n) {
	fact[0] = 1;
	for (int i = 1; i <= n; i ++) fact[i] = fact[i - 1] * i % p;
    infact[n] = inv(fact[n]);
    for (int i = n - 1; i >= 0; i --) infact[i] = infact[i + 1] * (i + 1) % p;
}

void solve() {
	int n, m; cin >> n >> m;
	cout << (fact[n] % p) * (infact[n - m] % p * infact[m] % p) % p << endl;
}


int main() {
	ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
	
	init(100005);
	int t; cin >> t;
	while (t --) {
		solve();
	}
	
	return 0;
}

卢卡斯定理

C n m = C n p m p × C n   m o d   p m   m o d   p C_n^m = C^{\frac{m}{p}}_{\frac{n}{p}} \times C^{m \ mod \ p}_{n \ mod \ p} Cnm=Cpnpm×Cn mod pm mod p

http://www.dtcms.com/a/113593.html

相关文章:

  • 4.4日欧篮联,NBA全扫盘,雷霆 vs 火箭单关预测已出
  • 来聊聊C++中的vector
  • C++学习之线程
  • [Android安卓移动计算]:新建项目和配置环境步骤
  • 力扣DAY35 | 热100 | LRU缓存
  • 在windows环境下通过docker-compose脚本自动创建mysql和redis
  • SQL Server常见问题的分类解析(二)
  • 分治-归并排序-逆序对问题
  • 计算机视觉图像处理基础系列:滤波、边缘检测与形态学操作
  • 小迪安全110-tp框架,版本缺陷,不安全写法,路由访问,利用链
  • Android使用OpenGL和MediaCodec渲染视频
  • AI浪潮下,“内容创作平台”能否借势实现内容价值跃升?
  • Turtle图形化编程知识点汇总:让编程更有趣
  • IDEA 2024.3.5 中修改 web.xml 的 Servlet 版本(比如从 4.0 修改为 5.0)
  • I.MX6ULL开发板与linux互传文件的方法--NFS,SCP,mount
  • AbstractBeanFactory
  • 基于SSM的车辆管理系统的设计与实现(代码+数据库+LW)
  • kd树和球树
  • Java中使用OpenCV实现怀旧滤镜时遇到的UnsatisfiedLinkError问题及解决方案
  • 一文读懂 MCP!
  • chromadb
  • Swift 扩展
  • 微服务架构与中台的关系
  • 高通camx ThreadManager
  • 【 <二> 丹方改良:Spring 时代的 JavaWeb】之 Spring Boot 的未来:从微服务到云原生的演进
  • Hyperlane框架:下一代高性能Rust Web框架 [特殊字符]
  • 学习笔记,DbContext context 对象是保存了所有用户对象吗
  • ring语言,使用vscode编辑器
  • AtCoder Beginner Contest 399 D,F 题解
  • 对迭代器模式的理解