基础数论

欧拉函数 φ(x)

https://oi-wiki.org/math/number-theory/euler/

φ(x)=k=1x[gcd(k,x)=1]表示 1x1 中与 x 互质的数。

欧拉函数是一个积性函数,但不是完全积性函数。

  • 积性函数:f(1)=1,且当 gcd(x,y)=1 时,f(xy)=f(x)f(y)
  • 完全积性函数:f(1)=1,且 x,y,f(xy)=f(x)f(y)

性质

  • φ(n)=n×pn,p is a primep1p
  • φ(pk)=pkpk1
  • gcd(i,n)=1i=φ(n)×n2

欧拉定理

gcd(k,n)=1,则有 kφ(n)1(modn)

证明

设序列 a1n 中与 n 互质的数,为 {a1,a2,a3,,aφ(n)},显然有以下两条性质:

  • i,有 gcd(ai,n)=1,也就是 ain 互质。这是显然的,因为这是欧拉函数的定义;
  • (i,j),有 aiaj(modn)。因为所有 ai 都是 <n 的。

现在找到一个数 k,满足 gcd(k,n)=1,也就是 kn 互质,并重新定义一个序列 b={ka1,ka2,ka3,,kaφ(n)},那么现在证明仍然满足上面的性质:

  • i,有 gcd(bi,n)=1,也就是 bin 互质。由于 bi=k×ai,又因为 kn 互质,ain 互质,所以 k×ai 也与 n 互质;
  • (i,j),有 bibj(modn)。利用反证法,若 bibj(modn),则这个式子可以变为 kaikaj(modn)。两边同时 ÷k,也就变成了 aiaj(modn),这与上面的结论不符。故 bibj(modn)。.

若将所有的 bin 取模,也就是 bi=kaimodn,那么根据上面的证明,可知 b 中的元素互不相同。而且由于有过取模,所以 bi 一定是在 [0,n) 的范围内的,所以 bin 互质。像这样大小为 φ(n),各不相同,且与 n 都互质的数构成的序列有且仅有一个,所以 b=a

所以可以得到:

i=1φ(n)ai=i=1φ(n)bi=i=1φ(n)(kaimodn)=(i=1φ(n)ai)×kφ(n)modn

等式两边同时 ÷i=1φ(n)ai,则有:

kφ(n)1(modn)

应用

  • 求逆元:1kkφ(p)1(modp)(当 p 为质数时)。
  • 降低指数:kakamodφ(p)(modp)
  • 扩展欧拉定理:p 可以不是质数,k 可以不与 p 互质。

ka{kaa<φ(p)kamodφ(p)+φ(p)aφ(p)(modp)

求解

Θ(n) 求一个 φ(n)φ(n)=n×pn,p is a primep1p

Θ(n)φ(1)φ(n):线性筛法:

  • i 是质数,显然 1i1 都与 i 互质,故 φ(i)=i1
  • 枚举 j,找到 pj×i,分两种情况求解 φ(pj×i)
    • imodpj=0,也就是 pji 的最小质因子。那么也就是说在原来的 i 中就已经有了一个质因子 pj,所以 ii×pj 的质因子是相同的。假设 i 的所有质因子是 qk,那么 i×pj 的质因子也是 qk,所以有 φ(pj×i)=pj×i×qi,q is a primeq1q。其中后面的 qi,q is a primeq1q 就是 φ(i) 的值,所以 φ(pj×i)=pj×φ(i)
    • imodpj0,也就是 pj 不是 i 的最小质因子。那么根据积性函数的定义,显然有 φ(pj×i)=φ(pj)×φ(i),也就是 φ(pj×i)=(pj1)×φ(i)
int p[N];		// p[i] 表示第 i 个质数
bool st[N];		// st[i] 表示 i 是否是质数
int phi[N];

void prime(int n)
{
	phi[1] = 1;
	for (int i = 2; i <= n; i ++ )
	{
		if (!st[i]) p[ ++ cnt] = i, phi[i] = i - 1;
		for (int j = 1; p[j] <= n / i; j ++ )
		{
			st[p[j] * i] = 1;
			if (i % p[j]) phi[i * p[j]] = phi[i] * p[j];
			else
			{
				phi[i * p[j]] = phi[i] * (p[j] - 1);
				break;
			}
		}
	}
}

除数函数 σk(x)

σk(x)=dxdk

k=0 时,含义为 n 的约数个数和。

k=1 时,含义为 n 的约数和。

除数函数全部是积性函数,但不是完全积性函数。

σ0 求解

Θ(n) 求一个 σ0(n):若 n=i=1mpici,那么根据乘法原理,有 σ0(n)=i=1m(ci+1)

Θ(n)σ0(1)σ0(n):线性筛法:

在线性筛约数个数时还需要额外记录一个 numi 数组,表示 i 的最小质因子的出现次数。

  • i 是质数,那么根据质数的定义,i 只有两个约数 1i,故 σ0(i)=2,而且显然 numi=1
  • 枚举 j,找到 pj×i,分两种情况求解 σ0(pj×i)
    • imodpj=0,也就是 pji 的最小质因子。那么 i 原来有 pj 这个约数,并且是最小的约数,现在的 pj×i 相比 i 又多了一个 pj,也就是原来的 ×(pj+1) 变成了 ×(pj+1+1),也就是 ×(pj+2)。所以求 σ0(pj×i) 时就需要先把原来 (pj+1) 的贡献移除,在重新加上 (pj+2) 的贡献,也就是 σ0(pj×i)=σ0(i)×pj+2pj1
    • imodpj0,也就是 pj 不是 i 的最小质因子。那么也就是说原来的 i 中没有 pj 这个约数,所以把 pj×i 分解质因数后应该有一项是 pj1,剩下的是原来 i 分解质因数的结果。根据约数个数的公式,将所有的指数加 1 后连乘,那么就应该在原来 σ0(i) 的基础上 ×(1+1),所以 σ0(pj×i)=2×σ0(i)
int p[N];		// p[i] 表示第 i 个质数
bool st[N];		// st[i] 表示 i 是否是质数
int d[N];		// d[i] 表示 σ0(i) 的值 
int num[N];		// num[i] 表示 i 的最小质因子出现次数 

void prime(int n)
{
	d[1] = 1;
	for (int i = 2; i <= n; i ++ )
	{
		if (!st[i]) p[ ++ cnt] = i, d[i] = 2, num[i] = 1;
		for (int j = 1; p[j] <= n / i; j ++ )
		{
			st[p[j] * i] = 1;
			if (i % p[j]) num[i * p[j]] = 1, d[i * p[j]] = d[i] * 2;		// p[j] 不是 i 的最小质因子 
			else 	// p[j] 是 i 的最小质因子 
			{
				num[i * p[j]] = num[i] + 1, d[i * p[j]] = d[i] / (num[i * p[j]] + 1) * (num[i * p[j]] + 2);
				break;
			}
		}
	}
}

莫比乌斯函数 μ(x)

https://oi-wiki.org/math/number-theory/mobius/

μ(n)={1n=10n(1)otherwise

性质:dnμ(d)={1n=10n1

莫比乌斯函数是积性函数,但不是完全积性函数。

求解

Θ(n) 求一个 μ(n)

Θ(n)μ(1)μ(n):线性筛法:

  • i 是质数,那么 i 就只有一个质因子 i,所以 μ(i)=(1)1=1
  • 枚举 j,找到 pj×i,分两种情况求解 μ(pj×i)
    • imodpj=0,也就是 pji 的最小质因子。设 q=pji,那么显然有 pj×i=q×i2,所以 pj×i 就有了一个平方因子,所以 μ(pj×i)=0
    • imodpj0,也就是 pj 不是 i 的最小质因子。那么 pj×i 就比 i 多了一个新的质因子 pj,故它的本质不同的质因子个数就多了 1,所以 μ(pj×i)=μ(i)
void prime(int n)
{
	mu[1] = 1;
	for (int i = 2; i <= n; i ++ )
	{
		if (!st[i]) p[ ++ cnt] = i, mu[i] = -1;
		for (int j = 1; p[j] <= n / i; j ++ )
		{
			st[p[j] * i] = 1;
			if (i % p[j]) mu[p[j] * i] = -mu[i];		// p[j] 不是 i 的最小质因子 
			else 	// p[j] 是 i 的最小质因子 
			{
				mu[i * p[j]] = 0;
				break;
			}
		}
	}
}

欧几里得算法

https://oi-wiki.org/math/number-theory/gcd/#%E6%AC%A7%E5%87%A0%E9%87%8C%E5%BE%97%E7%AE%97%E6%B3%95

gcd(a,b),辗转相除法。

gcd(a,b)=gcd(b,amodb)

gcd(b,amodb)=gcd(amodb,bmod(amodb))

gcd(amodb,bmod(amodb))=gcd(bmod(amodb),(amodb)mod(bmod(amodb)))

gcd(a,0)=a

回溯得到答案。

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

扩展欧几里得算法

https://oi-wiki.org/math/number-theory/gcd/#%E6%89%A9%E5%B1%95%E6%AC%A7%E5%87%A0%E9%87%8C%E5%BE%97%E7%AE%97%E6%B3%95

求二元一次方程 ax+by=c 的解。

裴蜀定理

裴蜀定理:有解当且仅当 gcd(a,b)c

证明:

  1. 有解当且仅当 gcd(a,b)c

    d=gcd(a,b),a=k1d,b=k2d,则 ax+by=k1dx+k2dy=(k1x+k2y)×d。这个数一定是 d 的倍数。证毕。

  2. gcd(a,b)ax+by 的最小整数解:

    d=gcd(a,b)max+by 的最小整数解,即 ax+by=m。接下来从两个方面证明 d=m

    1. md:与上面类似。设 a=k1d,b=k2d,则 ax+by=k1dx+k2dy=(k1x+k2y)×d=md

    2. md:因为 dadb,尝试证明 mamb。由于 d 是最大公约数,所以 md

      反证法:设 ma,则 a=km+r,r[1,m),则 r=akm。因为 m=ax+by,则 r=akaxkby,即 r=a(1kx)+b(ky),即得到一组新的解,使得 ax+by=r,于 m 是最小正整数结果矛盾,故 ma,同理 mb

    证毕。

求解

把这个方程中的 a,b 带入 gcd(a,b) 欧几里得算法中。

a1x1+b1y1=c

a2x2+b2y2=c

其中 a2=b1, b2=a1modb1=a1a1b1×b1

这样第二个式子就变成了 b1x2+(a1a1b1×b1)×y2

整理式子:

b1x2+a1y2a1b1×b1y2

a1y2+b1×(x2a1b1×y2)

至此,这个式子与最开始 a2x2+b2y2=c 变得很像,也就是说如果需要从第二个式子变到第一个式子需要 x1y2,y1(x2a1b1×y2)

这是第二层转换到第一层的过程,在这之前如果要求第二个式子需要往下的第三个式子进行推导,那么就需要写递归函数求解了。

当某个 ym=0 时,gcd 函数行该返回 xm,此时 xm=1,ym=任意数,这里 ym=0,返回函数即可。

代码:

int exgcd(int a, int b, int &x, int &y)
{
	if (!b)
	{
		x = 1, y = 0;
		return a;
	}
	int d = exgcd(b, a % b, x, y);
	int X = x;
	x = y, y = X - a / b * y;
	return 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);
    y -= (a / b) * x;
    return d;
}

如果 (x,y) 是一组解,那么 (x+bg,yag) 也是一组解,其中 g0

求逆元

x,使得 xk1(modp)

也就是求 x×k+y×p=1

于是就转化成了二元一次方程,求的是 x[0,p1] 区间内的一个解。

这个方法对 p 的素性没有要求。

int inv(int a, int b)
{
	int x, y, d = exgcd(a, b, x, y);
	if (d == 1) return (x % b + b) % b;
	else return -1;
}

筛质数

https://oi-wiki.org/math/number-theory/sieve/

  • 埃氏筛:Θ(n×ln(n))

从小到大枚举每一个数字,如果之前被筛掉了,那么不是质数,否则就是质数。

无论当前的 i 是不是质数,枚举已经筛出来的每一个质数 pmj,把 i×pmj 筛掉,直到 i×pmj 大于上街 n 或者质数枚举完毕结束。

这个筛法从来不用。

  • 线性筛:Θ(n)

对于每一个数字 x,仅在 pmj×i 的时候被筛掉,其中 pmjx 的最小质因数。

在枚举 pm 的时候,加一句判断,如果 pmji,那说明 i 的最小质因子为 pmj,之后再枚举 pm 就不满足要求了,直接 break

int p[N];
bool st[N];

void prime(int n)
{
	for (int i = 2; i <= n; i ++ )
	{
		if (!st[i]) p[ ++ cnt] = i;
		for (int j = 1; p[j] <= n / i; j ++ )
		{
			st[p[j] * i] = 1;
			if (i % p[j] == 0) break;
		}
	}
}

扩展

线筛除了可以筛质数,还可以筛一类满足条件的函数。

  • f(1),f(pm) 可以直接求(pm 指任意一个质数);
  • pmjx 的最小质因子,pmj×i=x

那么已知 f(pmj)f(i)Θ(1)Θ(logn) 求出 f 值。

中国剩余定理 CRT

https://oi-wiki.org/math/number-theory/crt/

有物不知其数,三三数之剩二,五五数之剩三,七七数之剩二。问物几何?

给定 ai,pi,其中 pi 两两互质,求 x,满足:

{xa1(modp1)xa2(modp2)xan(modpn)

定义一个 b 数组,bi{ai(modpi)0(modpji)

显然 x=i=1nbi

我们可以这样得到一个 biPpi×ai×(Ppi)pi2,其中 P=i=1npi

bi 可能很大,对 P 取模。

时间复杂度 Θ(nlogn)

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

signed main()
{
    cin >> n;
    
    int M = 1;
    for (int i = 0; i < n; i ++ )
    {
        cin >> A[i] >> B[i];
        M *= A[i];
    }
    
    int res = 0;
    for (int i = 0; i < n; i ++ )
    {
        int Mi = M / A[i], ti, x;
        exgcd(Mi, A[i], ti, x);
        res += B[i] * Mi * ti;
    }
    
    cout << (res % M + M) % M;
    
    return 0;
}

扩展中国剩余定理 EXCRT

如果 pi 不两两互质,换一个角度分析问题。

求解 x 实际上是合并方程。最终得到:

xA(modP)x=A+kP(kZ)

如果能把两个式子 xa1(modp1)xa2(modp2) 合并成 xa12modp12,那么就可以用一般方法不断地合并两个方程,直到仅剩一个方程,得到答案。其中 p12=lcm(p1,p2)p1p2a12 是原方程组的任意一个特解。

如何求 a12

  • xa(modp)x=a+kp(kZ)
  • {xa1(modp1)xa2(modp2){x=a1+k1p1x=a2+k2p2
  • k1p1k2p2=a2a1

根据裴蜀定理,上式有解当且仅当 gcd(p1,p2)a2a1

扩欧求出 k1 特解,带入得到 x 特解记为 a12

然后不断将方程两两合并即可找到 x

int exgcd(int a, int b, int &x, int &y)  // 扩展欧几里得算法, 求x, y,使得ax + by = gcd(a, b)
{
    if (!b)
    {
        x = 1; y = 0;
        return a;
    }
    int d = exgcd(b, a % b, y, x);
    y -= (a / b) * x;
    return d;
}

int a1, m1;     // 现有方程
int a2, m2;     // 读入的方程
int k1, k2;     // 系数
int d;          // 最大公约数
int t;

signed main()
{
    cin >> n >> a1 >> m1;
    
    for (int i = 0; i < n - 1; i ++ )
    {
        cin >> a2 >> m2;
        d = exgcd(a1, a2, k1, k2);
        if ((m2 - m1) % d)      // 裴蜀定理,判断是否无解
        {
            puts("-1");
            return 0;
        }
        
        k1 *= (m2 - m1) / d; // 把 k1a1 - k2a2 = m2 - m1 翻成 k1a1 - k2a2 = d
        t = a2 / d;
        k1 = (k1 % t + t) % t;
        
        m1 = a1 * k1 + m1;
        a1 = abs(a1 / d * a2);
    }
    
    cout << (m1 % a1 + a1) % a1;
    
    return 0;
}
posted @   2huk  阅读(31)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示