数论知识集锦(持续更新中)

数论还是比较重要的一部分,需要我们好好掌握
话不多说,直接上算法吧

1.线性筛

这还是比较简单的一部分了吧
线性筛是什么就不多说了,顾名思义即可
先讲讲普通筛
代码如下:

memset(isprime, true, sizeof(isprime));
isprime[1] = false;
for (int i = 2; i * i <= n; i++)
    for (int j = i; i * j <= n; j++)
        isprime[i * j] = false;

代码很简单,时间复杂度约为 O(nlog2log2n)
时间还能凑合,但是遇到 n=107 级别的时候就GG了
所以才要引进线性筛这种神奇的操作……
方法? 每一个数都只被自己的最小质因子筛一次
为什么普通筛时间会爆,因为一个数可能被筛了很多次……
看看线性筛的代码(比较丑陋)

memset(isprime, true, sizeof(isprime));
int len = 0; isprime[1] = false;
for (int i = 2; i <= n; i++) {
    if (isprime[i]) prime[++len] = i;
    for (int j = 1; j <= len && i * prime[j] <= n; j++) {
        isprime[i * prime[j]] = false;
        if (i % prime[j] == 0) break;
    }
}

这就是线性筛的代码了
如果你认为它只能筛素数,那就大错特错了……
在这里我们引入一些概念
积性函数:当 (a,b)=1 时,满足 f(ab)=f(a)f(b) ,则 f 为积性函数
完全积性函数:对于任意数对(a,b),都满足 f(ab)=f(a)f(b) ,则 f 为完全积性函数
常见的积性函数:
欧拉函数φ(n)表示在 1n 中和 n 互质的数的个数
约数个数d(n)表示 n 约数个数
约数和σ(n)表示 n 的约数和
线性筛可以解决积性函数求值的问题

我们拿欧拉函数φ作为例子
对于一个积性函数,我们需要考虑2种情况
1. n=pk p 为质数
2.n=pk11×pk22××pkmm( p1,k1,p2,k2pm,km 都为正整数)
当属于第一种情况的时候,很容易得到

φ(n)=pkpk1=n(11p)

当属于第二种情况的时候,可以通过质因数分解得到
φ(n)=φ(pk11)φ(pk22)φ(pkmm)=n(11p1)(11p2)(11pm)

所以线性筛即可

还是看一看具体代码比较好

memset(phi, 0, sizeof(phi));
int len = 0; phi[1] = 1;
for (int i = 2; i <= n; i++) {
    if (!phi[i]) {
        phi[i] = i - 1;
        prime[++len] = i;
    }
    for (int j = 1; j <= len && i * prime[j] <= n; j++) {
        int k = i * prime[j];
        if (i % prime[j] == 0) {
            phi[k] = phi[i] * prime[j];
            break;
        }
        phi[k] = phi[i] * (prime[j] - 1);
    }
}

和线性筛素数的代码大同小异
再举个例子,约数和 σ(n)
还是和求 φ(n) 一样的思路,分类讨论两种情况
1. n=pk
2. n=pk11×pk22××pkmm ( p1,k1,p2,k2pm,km 都为正整数)
第一种情况的 σ(n) 很好求
很显然

σ(n)=σ(np)+n=i=0kpi

第二种也差不太多,大同小异
σ(n)=σ(pk11)σ(pk22)σ(pkmm)=i=1mj=0kipji

这个和求 φ(n) 的过程差不多,就不写代码了

基本所有的积性函数怎么求值都可以这样分两步求
那么,线性筛这个部分就告一段落了

2.扩展欧几里得

欧几里得算法求最大公约数大家应该都会
时间复杂度约为 O(log2n)
引入一个定理:
对于一个不定方程 ax+by=c ,只有 (a,b)|c 的时候,方程有整数解
推论:若 (a,b)=1 ,则 ax+by=1 有解(很显然吧)
那么,扩展欧几里得就是一个可以求得这个方程的其中一组整数解的算法
方程为: ax+by=c,c=(a,b)
我们考虑使用欧几里得算法的思想,即为辗转相除法
a=bq+r,r=a mod b ,递归求出 bx+ry=c 的解
假设我们求出 bx+ry=c 的解为 x,y
a=bq+r 代入 ax+by=c ,则可以转化为:

b(xq+y)+rx=c

所以 x=xq+y,y=x
所以原始的解 x=y,y=xyq
递归求解即可
注意一下边界,当 b=0 时, x=1,y=0
下面放一放代码吧

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

代码确实比较简洁,但是写起来可能还是需要一定的思考和理解的
那么,可能你会问,这东西有什么用呢?
好,那我们来看看
来引进一个十分重要的概念,叫逆元

(1)逆元

ax1(modb) ,则称 x a关于模 b 的逆元
这个定义式其实等价于ax+by=1
只有当 (a,b)=1 时才会有逆元,否则不存在
我们一般这么写
a 的逆元为x1(modb),形象化说,就是 a11(modb)
那么,在模 b 意义下, a1=x 这样可能比较容易理解
求逆元直接用扩展欧几里得即可
显然,在 [0,b) 的范围内, a 关于模b的逆元(如果存在的话)是唯一的
反证法即可
只求一个很容易,那么我们要求 1n 关于模质数 p 的逆元呢?
并且n 106107 级别
我们来推导一下吧
首先, 111(modp) (十分明显)
那么,我们假设 p=ax+b,b<x,1<x<p
把这个式子带进去,就会得到 ax+b0(modp)
两边同时乘上 x1b1 ,可得

ab1+x10(modp)x1ab1(modp)x1pi×(p mod i)1(modp)

所以,逆元就可以推出来了

代码如下

for (inv[1] = 1, i = 2; i <= n; i++)
    inv[i] = (p - p / i) * inv[p % i] % p;

逆元这个东西还是十分重要的,在一些求方案数的dp中会大量使用,特别是遇到除法的时候

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值