数论还是比较重要的一部分,需要我们好好掌握
话不多说,直接上算法吧
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(n⋅log2log2n)
时间还能凑合,但是遇到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为积性函数
完全积性函数:对于任意数对
常见的积性函数:
欧拉函数
约数个数
约数和
线性筛可以解决积性函数求值的问题
我们拿欧拉函数
对于一个积性函数,我们需要考虑2种情况
1.n=pk,p为质数
2.
当属于第一种情况的时候,很容易得到
当属于第二种情况的时候,可以通过质因数分解得到
所以线性筛即可
还是看一看具体代码比较好
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,k2…pm,km都为正整数)
第一种情况的σ(n)很好求
很显然
第二种也差不太多,大同小异
这个和求φ(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,则可以转化为:
所以x′=xq+y,y′=x
所以原始的解x=y′,y=x′−y′q
递归求解即可
注意一下边界,当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)逆元
若ax≡1(modb),则称x是
这个定义式其实等价于
只有当(a,b)=1时才会有逆元,否则不存在
我们一般这么写
a的逆元为
那么,在模b意义下,
求逆元直接用扩展欧几里得即可
显然,在[0,b)的范围内,a关于模
反证法即可
只求一个很容易,那么我们要求1−n关于模质数p的逆元呢?
并且
我们来推导一下吧
首先,1−1≡1(modp)(十分明显)
那么,我们假设p=ax+b,b<x,1<x<p
把这个式子带进去,就会得到ax+b≡0(modp)
两边同时乘上x−1⋅b−1,可得
所以,逆元就可以推出来了
代码如下
for (inv[1] = 1, i = 2; i <= n; i++)
inv[i] = (p - p / i) * inv[p % i] % p;
逆元这个东西还是十分重要的,在一些求方案数的dp中会大量使用,特别是遇到除法的时候