J.数论
基础知识
一、同余
1. a同余2(mod n) a%n==2
2. (a+b)%n => (a%n+b%n)%n
3. (a*b)%n => (a%n)*(b%n)%n
二、欧几里得算法(辗转相除法)
1.作用:求最大公约数
2.表示:最大公约数(a,b) gcd(a,b) 最小公倍数 [a,b] lcm(a,b)
3.理论基础:(a,b)=(b,a mod b)
a和b的最大公约数等于b和a模上b的最大公约数
4.时间复杂度:O(logn)
//记住(a,b)=(b,a mod b),可以8和2为例
int gcd(int a, int b)
{
return b ? gcd(b, a % b) : a; //如果b不是0的话,返回b和a模上b的最大公约数
//否则,若b是0的话,返回a。因为0和一个数的最大公约数是他本身
}
5.最小公倍数:lcm(a,b)=(a*b)/gcd(a,b)
两个数的最小公倍数是这两个数的乘积除以他们的最大公约数
理解的话可以4 和 2 为例。
6.C++自带内置函数求最大公约数函数:__gcd(a,b)
三、算术基本原理(因式分解定理)
1.作用:是所有数论的基础。
2.内容:
N = P1^a1 * P2^a2 * … * Pn^an (pi为质数,ai>0)
则N的约数个数为(a1+1)(a2+1)…(an+1)
假设N的一个约数为D
D = P1^b1 * P2^b2 * … * Pn^bn
其中bi可以取到0,范围是0<= bi <= ai
因为只有和N的质因数一一对应一定能得到约数
因为bi可以从0取到ai,那么每一个bi就有(ai+1)种选法
约数的个数就是每个bi对应多少种选法相乘
即约数个数就为(a1+1)(a2+1)…(an+1)
约数之和S = (1+p1+p1^2+…+p1^a1)(1+p2+p2^2+…+p2^a2)…(1+pn+pn^2+…+pn^an)
四、线性筛法求素数(筛素数)
1.作用:可以在 O(n) 的时间复杂度内求出 1∼n 之间的所有质数以及每个数的最小质因子。
2.时间复杂度:O(n)
3.思想:
4.代码模板:
//一般需要三个变量:primes[]存所有质数,cnt表示质数的个数,st[]表示当前的数有没有被筛过
int primes[N], cnt;
bool st[N];
//筛出1~n中所有的质数
void get_primes(int n)
{
for (int i = 2; i <= n; i ++ ) //i从2开始,因为最小的质数是2
{
if (!st[i]) primes[cnt ++ ] = i; //如果当前的数没有被筛过的话,说明他没有质因子,就是一个质数,把他存到质数数组里
//cnt表示质数的个数,也就是用来控制数组下标。i就是要枚举的找的数
for (int j = 0; primes[j] <= n / i; j ++ )
{
st[primes[j] * i] = true;
if (i % primes[j] == 0)
break;
}
}
}
五、扩展欧几里得算法
1.裴蜀定理
若 a,b 是整数,且 (a,b)=d,那么对于任意的整数 x,y,ax+by 都一定是 d 的倍数,特别地,一定存在整数 x,y,使 ax+by=d 成立。
2.作用
扩展欧几里得算法可以在 O(logn) 的时间复杂度内求出系数 x,y。扩展欧几里德算法是用来解决裴蜀定理的,是在欧几里得算法求解最大公约数的过程中求出一组解x,y。
3.代码模板
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;
}
六、欧拉函数
1.内容:
2.性质:
3.作用:
下面的代码可以在 O(n)的时间复杂度内求出 1∼n 中所有数的欧拉函数:
4.代码模板:
int primes[N], euler[N], cnt;
bool st[N];
// 质数存在primes[]中,euler[i] 表示
// i的欧拉函数
void get_eulers(int n)
{
euler[1] = 1;
for (int i = 2; i <= n; i ++ )
{
if (!st[i])
{
primes[cnt ++ ] = i;
euler[i] = i - 1;
}
for (int j = 0; primes[j] <= n / i; j ++ )
{
st[primes[j] * i] = true;
if (i % primes[j] == 0)
{
euler[i * primes[j]] = euler[i] * primes[j];
break;
}
euler[i * primes[j]] = euler[i] * (primes[j] - 1);
}
}
}
例题
一、等差数列
1.解题思路
2.代码
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010; //数据范围
int a[N];
int gcd(int a, int b) //欧几里得算法
{
return b ? gcd(b, a % b) : a;
}
int main()
{
int n;
scanf("%d", &n);
for (int i = 0; i < n; i ++ ) scanf("%d", &a[i]);
sort(a, a + n); //给所有数排个序
int d = 0; //初始值为0,因为0和另一个数的最大公约数都是另一个数本身
for (int i = 1; i < n; i ++ ) d = gcd(d, a[i] - a[0]); //求公差:所有项与第一项的差的最大公约数
if (!d) printf("%d\n", n); //特判:如果d=0,所有数都一样,说明为常数序列
else printf("%d\n", (a[n - 1] - a[0]) / d + 1); //根据等差数列公式求出项数
return 0;
}