zoukankan      html  css  js  c++  java
  • 【讲课】基础的数论知识

    翻车是必然的




    gcd

    口胡吧。。。

    inline int gcd (int a, int b) {
    	return b ? gcd (b, a % b) : a;
    }
    

    exgcd

    先裴蜀

    (large x, yin Z^+), 使得 $large ax+by= m $ 成立的充要条件是 (large gcd (a,b) | m)

    之前讲的有 (large gcd (a, b) = gcd (b, b mod a)) 来找。。。

    继续设 (large s=gcd(a,b))

    假如真就有解

    明显就有 (large s|ax, s|by)

    然后证明就完了

    放在exgcd里

    只考虑 $large x, yin Z^+ large ax+by= gcd(a,b) $

    (large b=0) 时,(large gcd (a,b)=a) , 答案很显然了

    (large b eq 0) 时,

    [large{ egin{aligned} &a mod b = a-lfloorfrac{a}{b} floor b\ &ax+by\ &=gcd(a,b)\ &=gcd(b,a mod b)\ &=bx_2+(a mod b)y_2\ &=bx_2+ay_2-lfloorfrac{a}{b} floor by_2\ &=ay_2+b(x_2-lfloorfrac{a}{b} floor y_2) end{aligned} } ]

    就很显然的得出

    (large x=y_2,y=x_2-a/b*y_2)

    inline int exgcd (int &x, int &y, int a, int b) { // 取地址为了方便随时修改
        if (!b) {
            x = 1, y = 0;
            return a;
        }
        int gcd = exgcd (x, y, b, a % b);
        int t = x;
        x = y;
        y = t - a / b * y;
        return gcd;
    } // 顺便处理了一下gcd
    

    放个题

    luogu P2508

    [large { egin{aligned} &x^2+y^2=r^2\ &x^2=r^2-y^2=(r-y)(r+y) end{aligned} } ]

    [large{ egin{aligned} &设r-y=k*u,r+y=k*v,这里很明显gcd(u,v)=1\ \ &y=frac{v-u}{2}*k,2*r=(v+u)*k\ \ &x^2=k^2*u*v\ \ &那 u*v 一定是个平方数\ \ &但gcd(u,v)=1\ \ &所以 u,v 分别是个平方数\ \ &再设u=s^2,v=t^2\ \ &x^2=d^2*s^2*t^2\ \ &x=d*s*t\ \ &y=frac{v-u}{2}*k=frac{t^2-s^2}{2}*k\ \ &2*r=(v+u)*k=(t^2+s^2)*k\ end{aligned} } ]

    由此得出最后三条新关系式,我们暴力枚举 k , s ,从而算出整数 t (有可能不是整数),判断 (large gcd(t,s)=1),带入得到 (large x,y) ,符合题意就 ans+=8 (四个象限,(x,y)(y,x)算两个)。

    最后 ans+=4(r为整数,坐标轴还有4个)

    #include <bits/stdc++.h>
    
    #define ll long long
    
    using namespace std;
    
    template <typename T>
    inline void read (T &a) {
    	T x = 0, f = 1;
    	char ch = getchar ();
    	while (! isdigit (ch)) {
    		if (ch == '-') f = 0;
    		ch = getchar ();
    	}
    	while (isdigit (ch)) {
    		x = (x << 1) + (x << 3) + (ch ^ '0');
    		ch = getchar ();
    	}
    	a = f ? x : -x;
    }
    
    inline ll gcd (ll a, ll b) {
        if (!b) return a;
        return gcd (b, a % b);
    }
    
    ll r, ans, x, y;
    
    inline void blanc (ll k) {
        for (ll s = 1, t; s * s <= r / k; s++) {
            t = sqrt (r / k - s * s);
            if (s * s + t * t == r / k) {
                if (gcd (s, t) == 1) {
                    x = k * s * t;
                    y = (t * t - s * s) / 2 * k;
                    if (x > 0 and y > 0 and x * x + y * y == r / 2 * (r / 2)) {
                        // 只考虑一象限,判断整数和是否满足条件,r先除后乘防爆ll
                        ans += 8;
                    }
                }
            }
        }
    }
    
    signed main () {
        read (r);
        r *= 2;
        for (ll k = 1; k * k <= r; k++) {
            if (! (r % k)) { // 优先级!优先级!!!!!
                blanc (k);
                if (r / k != k) blanc (r / k);
            }
        }
        printf ("%lld", ans + 4);
    }
    

    再放一个

    讲完逆元再讲,记得提醒我

    [large {} egin {aligned} &b * b^{-1}equiv1 (mod p)\ &b*inv(b)equiv1 (mod p)\ &b^{-1}equiv inv(b) (mod p)\ &a*b^{-1}equiv a*inv(b) (mod p)\ &a*b^{-1}equiv a*inv(b) (mod p)\ &a*inv(b)equiv ? (mod p) end {aligned} ]

    洛谷P2613

    #include <bits/stdc++.h>
    
    #define mod 19260817
    #define ll long long
    
    using namespace std;
    
    template <typename T>
    inline void read (T &a) {
    	T x = 0, f = 1;
    	char ch = getchar ();
    	while (! isdigit (ch)) {
    		if (ch == '-') f = 0;
    		ch = getchar ();
    	}
    	while (isdigit (ch)) {
    		x = (x << 1) + (x << 3) + (ch ^ '0');
            x %= mod;
    		ch = getchar ();
    	}
    	a = f ? x : -x;
    }
    
    ll x, y;
    inline void exgcd (ll &x, ll &y, ll a, ll b) {
        if (!b) {
        	x = 1, y = 0;
            return ;
        }
        exgcd (x, y, b, a % b);
        ll t = x;
        x = y;
        y = t - a / b * y;
    }
    ll a, b;
    
    signed main() {
        read (a);
        read (b);
    
        if (b) {
            exgcd(x, y, b, mod);
    	    x = (x % mod + mod) % mod;
        	printf("%lld
    ", a * x % mod);
            return 0;
        }
        cout << "Angry!" << endl;
    }
    

    素数

    • 这一栏 copy 自大半年前的课件

    素数是啥不用说

    这里说一说怎么找素数,也就是素数筛法

    最朴素的

    int pri[MAXN];
    int cnt;
    inline void prime (int n) {
    	cnt = 0;
    	pri[++cnt] = 2;
    	int i,j;
    	for (i = 3; i <= n; ++i) {
    		for (j = 2; j < i; ++j) {
    			if (i % j == 0)
    			break;
    		}
    		if (j >= i)
    		pri[++cnt] = i;
    	}
    }
    // 如你所见,就是纯暴力
    

    过于垃圾,无法接受,直接pass

    好一点的

    埃氏筛
    埃式筛法的基本思想就是,当我们遍历到一个素数时,把所有该素数的倍数都筛选出来。

    int cnt = 0;
    int pri[20000];
    bool v[20000];
    int n;
    inline void prime () {
    	read (n);
    	for (int i = 2; i <= n; ++i) {
    		if (! v[i]) {
    			pri[++cnt] = i;
    			for (int j = i; j <= n; j += i) {
    				v[j] = true;
    			}
    		}
    	}
    	for (int i = 1; i <= cnt; i++) cout << pri[i] << " ";
    }
    
    

    埃式筛法很容易理解,并且在效率上也比较优秀,时间复杂度为 (O(N logN))

    埃氏筛在筛选时有重复,或许我们可以通过某种方法避免这种重复

    目前最好的

    欧拉筛

    int prime[MAXN];//它存的是最小素因子
    bool vis[MAXN];
    
    inline void phigros (int n) {
    	for (int i = 2; i <= n; ++i) {
    		if (! vis[i]) {
    			prime[++prime[0]] = i;
    		} // 判断素数
    
    		for (int j = 1; j < prime[0] and i * prime[j] < n; j++) {
    			vis[i * prime[j]] = true;//筛数
    			if(i % prime[j] == 0)//时间复杂度为O(n)的关键!
    				break;
    		}
    	}
    }
    
    

    为什么 i % prime[j] == 0 就break?
    当 i是prime[j]的倍数时,i = k * prime[j],如果继续运算 prime[j + 1],

    i * prime[j + 1] = prime[j] * (k * prime[j + 1]),
    这里prime[j]是最小的素因子,
    当i = k * prime[j+1]时,同样会在里循环中运算到,会重复,所以才跳出循环

    线性筛还有其他用法

    线性筛

    筛素数

    bool vis[N];
    int pri[N], cnt;
    inline void OSU () {
        for (int i = 2; i <= n; i++) {
            if (! vis[i]) pri[++cnt] = i;
            for (int j = 1; j <= cnt and i * pri[j] <= N; i++) {
                vis[i * pri[j]] = 1;
                if (i % pri[j] == 0) break;
            }
        }
    }
    

    筛欧拉函数

    容斥证明以下式子:

    [large varphi(n)=nprod_{i = 1}(1-frac{1}{p_i}) ]

    (large varphi) 积性函数证明:

    [large{ egin{aligned} &varphi(a)=aprod_{i=1}^m(1-frac{1}{p_i})\ &varphi(b)=bprod_{i=1}^n(1-frac{1}{q_i})\ &(a,b)=1\ &varphi(a)varphi(b)=aprod_{i=1}^m(1-frac{1}{p_i})bprod_{j=1}^n(1-frac{1}{q_j})\ &=abprod_{i=1}^{m+n}(1-frac{1}{c_i}) end{aligned} } ]

    线性筛 (largevarphi)

    1. (large (i,pri[j])=1) 过于简单,不予讨论

    2. (large i) 为质数,过于简单,不予讨论

    3. [large{ egin{aligned} &(i,pri[j])=pri[j]\ &varphi(i)=iprod_{k=1}(1-frac{1}{p_k})\ &pri[j]in p\ &varphi(i*pri[j])=i*pri[j]prod_{k=1}(1-frac{1}{p_k})=varphi(i)*pri[j] end{aligned} } ]

    bool vis[N];
    int pri[N], cnt;
    int phi[N];
    
    inline void Arcaea () {
        for (int i = 2; i <= n; i++) {
            if (! vis[i]) pri[++cnt] = i, phi[i] = i - 1;
            for (int j = 1; j <= cnt and i * pri[j] <= N; i++) {
                vis[i * pri[j]] = 1;
                if (i % pri[j] == 0) {
                    phi[i * pri[j]] = phi[i] * pri[j];
                    break;
                }
                phi[i * pri[j]] = phi[i] * (pri[j] - 1); // 互质,积性函数
            }
        }
    }
    

    筛约数个数和,约数和

    个数

    (large d(n))(large n) 的约数个数

    (large num(n))(large n) 的最小质因子个数

    因为 (large pri) 从小到大枚举,所以 (large pri[j]) 一定是 (large i imes pri[j]) 的最小质因子,毕竟线性筛的原理就在于此。

    唯一分解定理:

    [large n=prod_{i=1}p_i^{k_i} ]

    每个 (large p_i) 都可选择 ([0,k_i]) 个,彼此相乘,组成新约数。

    (large n) 的约数个数为:

    [large d(n)=prod_{i=1}(k_i + 1) ]

    1. (large i) 是质数,答案显然

    2. (large i\%pri[j]!=0) 相当于新加了一个质因子

      [large{ egin{aligned} &d(i imes pri[j])=d(i) imes d(pri[j])\ &=2 imes d(i)\ &num(i imes pri[j])=1 end{aligned} } ]

    3. (large i\%pri[j]==0) 之前出现过

      [large{ egin{aligned} &d(i imes pri[j])=(1+k_1+1)prod_{i=2}(k_i+1) end{aligned} } ]

      之前的 (large num) 起到了作用。

      [large d(i imes pri[j])=d(i)/(num(i)+1) imes(num(i)+2) ]

    约数和

    (large sd(n)) 表示 (large n) 的约数和(不是质因子和

    (large num(i)) 表示

    根据算数基本定理有:

    [large sd(n)=prod_{i=1}(sum_{j=0}^{r_i}p_i^j) ]

    记录最小质因子那一项,即 (large (1+p_1+p_1^2+cdots+p_1^{r_1}))

    (large num(n)) 表示。

    1. (large i) 是质数

      [large{ egin{aligned} &sd(i)=i+1\ &num(i)=i+1 end{aligned} } ]

    2. (large i\%pri[j]!=0) 显然

      [large{ egin{aligned} &sd(i*pri[j])=sd(i)* sd(pri[j])\ &num(i*pri[j])=pri[j]+1 end{aligned} } ]

    3. (large i\%pri[j]==0) 显然 (large num(i*pri[j]) = num(i)*pri[j]+1)

      [large sd(i*pri[j])=sd(i)/num(i)*num(i*pri[j]) ]

    代码略。。。(懒得打了

    筛莫比乌斯函数

    由定义式:

    [large mu(m) = egin{cases} (-1)^r & ext{$m = p_1p_2...p_r$}\ 0 & ext{$p_k^2|m$} end{cases} ]

    知,

    1. (large i) 为质数,(large mu(i)=-1).
    2. (large i\%pri[j]==0)(large mu(i*pri[j])=0).
    3. 否则 (large mu(i*pri[j])=-mu(i)).

    莫得代码

    米勒拉宾检验,这里不讲

    CRT

    求解同余方程组

    [large { egin{cases} x equiv a_1(mod m_1)\ x equiv a_2(mod m_2)\ x equiv a_3(mod m_3) & ext{$gcd(m_t,m_s)=1$},1 leq t,sleq k \ ...\ x equiv a_k(mod m_k)\ end{cases} } ]

    举例

    一个数 (n) ,除以3余2,除以5余3,除以7余2,求 (n)

    [large egin{cases} n_1 equiv a_1 (mod m_1)\ n_2 equiv a_2 (mod m_2)\ n_3 equiv a_3 (mod m_3)\ end{cases} ]

    (large m_1,m_2,m_3)两两互质 求共解

    先不找最小解

    考虑

    [large{ egin{aligned} &m_2*m_3|n_1\ \ &m_1*m_3|n_2\ \ &m_1*m_2|n_3\ end{aligned} } ]

    (n=n_1+n_2+n_3) 即是一个解

    (n mod lcm(m_1, m_2, m_3)) 就是最小解

    问题是咋求出来满足条件的 (n_1,n_2,n_3)

    用逆元(inv)

    举例

    [large{ egin{aligned} &n_1 equiv a_1 (mod m_1)\ &n_1^`=m_2*m_3\ &n_1^` * inv(n_1^`) equiv 1 (mod m_1)\ &n_1^`=inv(n_1^`)*m_2*m_3\ &n_1=a_1*n_1^`=a_1*inv(n_1^`)*m_2*m_3 end{aligned} } ]

    EXCRT

    CRT 是 EXCRT 的特殊情况。

    [large egin{cases} xequiv a_1 (mod m_1)\ xequiv a_2 (mod m_2)\ cdots\ xequiv a_n (mod m_n) end{cases} ]

    模数之间再无任何瓜葛

    [large{ egin{aligned} &x=a_1+k_1*m_1=a_2+k_2*m_2\ &a_1+k_1*m_1=a_2+k_2*m_2\ &k_2*m_2-k_1*m_1=a_1-a_2\ end{aligned} } ]

    嘶~最后的式子有些眼熟啊,像不像内个,对,就是内个!(ax+by=c)

    [large{ egin{aligned} &g=gcd(m_1,m_2)\ &c=a_1-a_2\ end{aligned} } ]

    如果 (large g mid c) 的话。。。那就无解。-_-

    有解,求 (large k_2 imes m_2+k_1 imes m_1=g)(large k_1)

    因为 (large gmid c),所以 (large k_1*=(c/g))

    防止爆炸,(large k_1\%=m_2)

    于是可以反推 (large x)

    但我想各位已经发觉到了,原方程式是 (large k_2 imes m_2-k_1 imes m_1=c)

    于是 (large x = -k_1 imes m_1+a_1),我们暂且设这个 (large x)(large x_0)

    通解就是 (large x=x_0+k imes lcm(m_1,m_2))

    于是这两个同余方程合并成了一个:

    (large x=x_0 (mod lcm(m_1,m_2)))

    其他方程间以此类推。

    最后剩下一个方程,他的 (large x_0) 的最小非负整数解,就是最终答案。

    例题:P4777 ~~这题怎么是个紫的?快降蓝啊(doge

    #include <bits/stdc++.h>
    
    #define int long long
    #define N 1000005
    
    using namespace std;
    
    template <typename T>
    inline void read (T &a) {
    	T x = 0, f = 1;
    	char ch = getchar ();
    	while (! isdigit (ch)) {
    		(ch == '-') and (f = 0);
    		ch = getchar ();
    	}
    	while (isdigit (ch)) {
    		x = (x << 1) + (x << 3) + (ch ^ '0');
    		ch = getchar ();
    	}
    	a = f ? x : -x;
    }
    
    int n, x, y, A, B, C;
    int m[N], a[N], ans;
    
    inline int qmul(int a, int b, int mo) {
        int ans = 0, base = a;
        while (b) {
            if (b & 1) {
            	ans = (ans + base) % mo;
            }
            base = (base + base) % mo;
            b >>= 1;
        }
        return ans;
    } // 龟速乘(不是快速幂)目的是防止两数相乘的时候溢出爆炸 
    
    inline int exgcd(int a, int b, int &x, int &y) {
        if (!b) {
            x = 1;
    		y = 0;
            return a;
        }
        int g = exgcd(b, a % b, x, y);
        int tx = x;
        x = y;
        y = tx - (a / b) * y;
        return g;
    } // 不多言,都知道 
    
    inline void excrt() {
        for (int i = 2; i <= n; i++) {
            A = m[1], B = m[i], C = a[i] - a[1];
            C = (C % B + B) % B;
    		// 防止 C 是个负的,用 B 是因为在同余式中 B 是模数 
            int g = exgcd(A, B, x, y);
            x = qmul(x, (C / g), B); // 这里的 x 就是博客里的 k1 
            x = (x % B + B) % B;
    		// k1 不能是个负的,负的难算还可能会错 
            a[1] = a[1] + qmul(m[1], x, m[1] * (m[i] / g));
            // 原博客 x = -k1 * m1 + a1 
            m[1] = m[1] * (m[i] / g); // 模数变为 lcm (m1, m2) 
            a[1] = (a[1] % m[1] + m[1]) % m[1]; // 防爆 
        }
    }
    
    signed main() {
        read (n); 
        for (int i = 1; i <= n; i++) {
        	read (m[i]), read (a[i]);
        }
        excrt();
        printf("%lld", a[1]);
    }
    

    逆元

    扩欧

    最常用的,经常在各大题解里见到。。。(比如上面那篇代码

    (large a * inv(a) = 1 (mod p))
    根据 exgcd 可以变形成 (large a * inv(a) +k * p = 1)

    这说明只有 a 和 p 互质才存在逆元

    inline int exgcd (int &x, int &y, int a, int b) { // 取地址为了方便随时修改
        if (!b) {
            x = 1, y = 0;
            return a;
        }
        int gcd = exgcd (x, y, b, a % b);
        int t = x;
        x = y;
        y = t - a / b * y;
        return gcd;
    } // 顺便处理了一下gcd
    
    inline long long inv (int a, int p) {
        long long x, y;
        long long d = exgcd (x, y, a, p);
        return d == 1 ? (x % p + p) % p : -1;
    }
    

    欧拉

    这个比扩欧快一点,但不指望你们掌握,(下边有更好的)

    [large a^{varphi(p)} equiv 1 (mod p)\ large a*a^{varphi(p)-1} equiv 1 (mod p) ]

    (large a^{varphi(p)-1}) 即是。

    inline ll phi (int p) {
    	int a, b;
    	a = b = p;
    	for (int i = 2; i * i <= a; ++i) { // 根号就够了
    		if (a % i == 0) {
    			b -= b / i; // 等价于 b = b * (1 - 1 / i), 精度问题 
    			while (a % i == 0) {
    				a /= i; // 把这几个质因子全去掉。。。 
    			}
    		}
    	}
    	if (a > 1) { // 应对素数情况 , 同时将最后一个没有因数用掉 
    		b = b - b / a;
    	}
    	return b;
    }
    
    inline ll fpow (ll a, ll p, ll mod) {
        ll ans = 1, cnt = a % mod;
        while (p) {
            if (p & 1) ans = ans * cnt % mod;
            cnt = cnt * cnt % mod;
            p >>= 1;
        }
        return ans;
    }
    
    inline ll inv (ll a, ll p) {
        return fpow (a, phi (p), p);
    }
    

    递推

    线性

    套用小绿书上的

    p 是模数, i 为待求逆元的数, 我们现在求 (i^{-1}) 在 mod p 意义下的值

    [large { egin{aligned} &p=k*i+r,k=p/i,r=p\%i\ &k*i+requiv0(mod p)\ & ext{all*$(ri)^{-1}$} \ &k*r^{-1}+i^{-1}equiv0(mod p)\ &i^{-1}equiv-k*r^{-1}(mod p)\ &i^{-1}equiv-p/i*inv(p\%i)(mod p)\ end{aligned} } ]

    (i^{-1}) 就是 inv[i] ,

    ll inv[p+5];
    inline void Inv (ll p) {
        inv[1] = 1;
        for (int i = 2; i < p; i++) {
            inv[i] = (p - p / i) * inv[p % i] % p;
            // (p - p / i) 在 mod p 意义下等价于 -(p/i);
        }
    }
    

    适用于 p 是不大的素数且逆元被多次调用

    预处理 (large O(p)),单次查询 (large O(1))

    递归

    把上面的 for 变一下

    inline ll inv (int i) {
        if (i == 1) return 1;
        return (p - p / i) * inv (p % i) % p;
    }
    

    单次查询 (large O(log p)).

  • 相关阅读:
    第10组 Alpha冲刺(4/4)
    python2中的新式类与经典类区别
    http请求Content-Type有几种
    2020 年软件设计师考试大纲
    11-Elasticsearch之-组合查询
    16-扩展阅读-摘录
    各种排序算法
    Vmware虚拟机三种网络模式详解(转载)
    无法访问 CentOS7服务器上应用监听的端口
    VsCode 常用插件清单
  • 原文地址:https://www.cnblogs.com/codingxu/p/15327729.html
Copyright © 2011-2022 走看看