• 【知识总结】数论全家桶


    NOI2019 不到一周了,开始总复习 颓废 咯 ~

    一些数论算法以前已经写过了,本文主要用于总结一些杂七杂八的小算法,查缺补漏。

    传送门 (说到传送门,Steam 夏日大促 Portal 2 只要 3 CNY 就是一玩起来就 3D 眩晕没法学习)

    本文将涉及以下内容:

    • 更相减损术、欧几里得算法及扩展欧几里得算法

    • 逆元

    • 中国剩余定理及扩展中国剩余定理

    • Lucas 定理

    本文中所有字母如无特殊说明均默认为正整数。

    更相减损术、欧几里得算法及扩展欧几里得算法

    更相减损术

    首先一个很显然的结论:

    [gcd(a,b)=gcd(a,a-b)(ageq b) ]

    (证明略,反正看起来很显然就对了 —— 虽然这个显然的结论经常想不到 QAQ )

    于是可以就照着这个一直递归(或者迭代)下去,边界是 (gcd(a,0)=a)

    欧几里得算法

    这就是大家熟悉的 (gcd(a,b)=gcd(a mod b, b)) ,可以从上面那个结论推过来。同样可以通过递归的方式计算:

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

    听说有时候更相减损术因为不用取模反而比这个还快?但是为什么我从来没见人写过更相减损术??

    扩展欧几里得算法

    这玩意可不仅仅是算个最大公因数那么简单,它是用来解方程的 ……

    问题:对于如下的方程(未知数是 (x)(y) ),求任意一组整数解:

    [ax+by=gcd(a,b) ]

    (m=a-bcdotlfloorfrac{a}{b} floor) (即 (a)(b) ),设 (x')(y') 满足以下方程:

    [bx'+my'=gcd(m, b) ]

    因为 (gcd(a,b)=gcd(m,b)) ,所以:

    [bx'+my'=ax+by ]

    (m) 按照定义展开:

    [egin{aligned} ax+by&=bx'+(a-bcdotlfloorfrac{a}{b} floor)y'\ &=bx'+ay'-bcdotlfloorfrac{a}{b} floor y'\ &=ay'+b(x'-lfloorfrac{a}{b} floor y') end{aligned}]

    因此 (x=y')(y=x'-lfloorfrac{a}{b} floor y') 。于是我们得到了一组解。

    然而,这是一个不定方程,解并不是唯一的。对于其中一组解 ({x_0,y_0}) ,有通解 ({x_0+kb,y_0-ka})

    形如 (ax+by=c) 都可以通过转化来用扩展欧几里得解决。设 (g=gcd(a,b)) ,如果 (g) 不能整除 (c) ,则原方程无整数解;否则,先解出 (ax+by=g) 的解,然后给解乘上 (frac{c}{g}) 即可。

    另外,线性同余方程 (axequiv bpmod p) 也可用扩展欧几里得解决,转化成 (ax+py=b) 即可。

    int exgcd(const int a, const int b, int &x, int &y)
    {
    	if (!b)
    	{
    		x = 1, y = 0;
    		return a;
    	}
    	int xx, yy, tmp = exgcd(b, a % b, xx, yy);
    	x = yy;
    	y = xx - a / b * yy;
    	return tmp;
    }
    

    逆元

    定义:若 (ab=1pmod m) ,则 (a)(b) 在模 (m) 意义下的逆元。这里介绍它的三种求法:

    费马小定理

    对于 质数 (p) 和任意正整数 (a) ,有 (a^{p-1}=1pmod p) 。因此在模 (p) 意义下 (a) 的逆元是 (a^{p-2}) ,直接快速幂计算即可。

    int power(int a, int b)
    {
    	int ans = 1;
    	while (b)
    	{
    		if (b & 1)
    			ans = (ll)ans * a % p;
    		a = (ll)a * a % p;
    		b >>= 1;
    	}
    	return ans;
    }
    int inv(const int a, const int p)
    {
    	return power(a, p - 2);
    }
    

    扩展欧几里得

    相当于同余方程 (axequiv 1pmod m) ,直接用扩展欧几里得解方程即可。

    exgcd 的实现见上文。

    int inv(const int a, const int p)
    {
    	int x, y, tmp = exgcd(a, p, x, y);
    	if (tmp > 1)
    		return -1;
    	return (x % p + p) % p;
    }
    

    线性递推逆元

    (这玩意我学了 N 遍甚至拿这个式子当电脑桌面一个月都没记住,很气。)

    (a^{-1}) 表示 (a) 的逆元。

    [a^{-1}= m-lfloorfrac{m}{a} floorcdot (mmod a)^{-1}pmod m ]

    边界是 (1^{-1}=1)

    证明:记 (t=lfloorfrac{m}{a} floor)(k=(mmod a)) ,则 (at+k=0pmod m)

    两边同时乘上 (a^{-1}cdot k^{-1}) ,得到:

    [tk^{-1}+a^{-1}=0 ]

    即:

    [a^{-1}=-tk^{-1} ]

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

    中国剩余定理及扩展中国剩余定理

    中国剩余定理

    大名鼎鼎的 CRT (Chongqing Rail Transit) (Chinese Remainder Theorem) 。

    对于如下方程组:

    [egin{cases} x=a_1pmod {m_1}\ x=a_2pmod {m_2}\ cdots\ x=a_npmod {m_n}\ end{cases}]

    其中 (m_1)(m_n) 两两互质。

    我们可以直接构造出一个解:

    [x=sum_{i=1}^{n}a_ifrac{M}{m_i}cdot mathrm{inv}(frac{M}{m_i},m_i) ]

    其中 (M=prod_{i=1}^n m_i)(mathrm{inv}(a,b)) 表示 (a) 在模 (b) 意义下的逆元。由于模数两两互质,此时逆元一定存在。

    对于任意一个 (i) ,当模 (m_i)(frac{M}{m_i}cdot mathrm{inv}(frac{M}{m_i},m_i))(1) (逆元的定义),否则为 (0)(frac{M}{m_i} mod m_j=0(i eq j))),因此这个 (x) 是原方程组的解。可以证明在模 (M) 的意义下有且只有这一个解。

    int CRT(const int n)
    {
    	int M = 1, ans = 0;
    	for (int i = 0; i < n; i++)
    		M *= p[i];
    	for (int i = 0; i < n; i++)
    		ans = (ans + (ll)a[i] * inv(M / p[i], p[i]) % M * (M / p[i]) % M) % M;
    	return ans;
    }
    

    扩展中国剩余定理

    仍然是上面的问题,只是 (m_1)(m_n) 不保证两两互质了。

    考虑如果只有两个方程如何求解:

    [egin{cases} x=a_1pmod {m_1}\ x=a_2pmod {m_2} end{cases}]

    对于第一个方程而言,解的形式是 (x=a_1+km_1) 。带入第二个方程,得到:

    [a_1+km_1=a_2pmod {m_2} ]

    这个方程中只有 (k) 是未知的。用扩展欧几里得解出来一个 (k_0) ,则任意 (km_1=k_0m_1+zm_2) 都是合法的(实在不知道用什么字母就用「嘴子」的第一个字母 (z) 好了),所以 (zm_2=ucdotmathrm{lcm}(m_1,m_2)) (实在不知道用什么字母就用「嘴子」的第二个字母 (u) 好了)。现在,(x) 的表现形式为 (a_1+k_0m_1+ucdotmathrm{lcm}(m_1,m_2)) ,我们合并出了一个新的方程:

    [x=a_1+k_0m_1pmod{mathrm{lcm}(m_1,m_2)} ]

    你看着像不像风靡机房的小游戏 全民合车 全民漂移(划掉)。

    于是这样合并 (n) 次就完了。

    顺便这里有一个叫「龟速乘」的小 trick ,利用整数溢出是循环溢出的特性制成 ……

    ll mul(ll a, ll b, const ll p)
    {
    	a %= p, b %= p;
    	return ((a * b - ll((long double)a * b / p) * p) % p + p) % p;
    }
    ll excrt()
    {
    	ll M = m[1], x = a[1];
    	for (int i = 2; i <= n; i++)
    	{
    		ll tmp = lcm(M, m[i]), res = solve(M, a[i] - x, m[i]);
    		if (res == -1)
    			return -1;
    		x = (x + mul(res, M, tmp)) % tmp;
    		M = tmp;
    	}
    	return x;
    }
    

    Lucas 定理

    就是一个公式,证明什么的 NOI 以后再补。

    [C_n^m=C_{lfloorfrac{n}{p} floor}^{lfloorfrac{m}{p} floor}cdot C_{nmod p}^{mmod p} pmod p ]

    其中 (p) 是质数。

  • 相关阅读:
    IP分片与TCP分片的考虑
    snort-2.9.16在ubuntu16.04环境下的安装,配置与运行
    snort 程序流程图
    宿主机、容器、真实时间不一致问题
    ffi动态链接库的使用
    docker容器中nginx日志的分割
    dockerFile指令详解
    关于如何查看多网卡物理机中网卡序号与物理网卡的对应该关系
    Curl相关参数意义及使用方式
    docker 基础入门
  • 原文地址:https://www.cnblogs.com/zyt1253679098/p/11179050.html
Copyright © 2020-2023  润新知