算法数学笔记-一、数论
一、初等数论
素数与约数的一些常用结论
素数数量,对1~n,约有素数 π(n) ≈ n/ln n
在 N! 中质因子p的个数为
N 的正约数的个数为
N的正约数的和为
扩展欧几里得与裴蜀定理
扩展欧几里得
解同余方程ax + by = gcd(a, b)
int exgcd(int a, int b, int &x, int &y){
if(b == 0) { x = 1, y = 0; return a;}
int d = exgcd(b, a % b, x, y);
int z = x; x = y; y = z - y * (a / b);
return d; // d = gcd(a, b);
}
求aX + bY = s的通解:
exgcd(a, b, x, y);
裴蜀定理
设a,b是不全为零的整数,若存在ax + by = c,则必有 gcd(a, b) | c
可扩展至多个数字(eg. ax + by + cz = d 必有 gcd(a, b, c) | d)
扩展欧拉定理与费马小定理
费马小定理
扩展欧拉定理
逆元
扩展欧几里得
线型推法
inv[1] = 1;
inv[i] = 1ll * (mod - mod / i) * inv[mod % i] % mod;
求任意n个数的逆元
计算前缀积s[i], 再计算s[n]的逆元sinv[n], sinv[i] = sinv[i + 1] * a[i + 1], inv[i] = sinv[i] * s[i - 1];
vec GetInvSum(vec a){
int n = a.size() - 1;
vec s(n + 1), sinv(n + 1);
s[0] = a[0];
for(int i = 1; i <= n; i ++)
s[i] = 1ll * s[i - 1] * a[i] % mod;
sinv[n] = GetInv(s[n]);
for(int i = n - 1; i >= 0; i --)
sinv[i] = 1ll * sinv[i + 1] * a[i + 1] % mod;
for(int i = 1; i <= n; i ++)
sinv[i] = 1ll * sinv[i] * s[i - 1] % mod;
return sinv;
}
阶、原根、二次剩余
阶:
因此满足同余式 的最小正整数n存在,这个n称作a模m的阶,记作。
性质:
两两互不同余
若则; 若则有
!!!WA: 若,则
原根
原根的判定:
原根的个数:
原根存在定理:
最小原根数量级:
模2的原根为1.
inline long long getG(long long x){
static long long q[10051];
long long top = 0, flg = 1;
for(register int i = 2; i * i <= x - 1; i ++){
if((x - 1) % i == 0){
q[++ top] = i;
if(i * i != x - 1)
q[++ top] = (x - 1) / i;
}
}
for(register int i = 2; ; i ++){
flg = 1;
for(register int j = 1; j <= top; j ++)
if(quickpow(i, q[j], x) == 1){
flg = 0;
break;
}
if(flg) return i;
}
二次剩余
namespace Shengyu2{
LL I, p, mod;
struct Complex2{ // 此complex为二次剩余专用
LL real, fals;
Complex2(double a = 0, double b = 0){
real = a, fals = b;
}
Complex2 operator * (const Complex2 & x){
LL a, b;
a = (real * x.real + fals * x.fals % mod * I) % mod;
b = (real * x.fals + fals * x.real) % mod;
return Complex2(a > 0 ? a : a + mod, b > 0 ? b : b + mod);
}
Complex2 operator % (const LL & mod){
real = real % mod;
fals = fals % mod;
return Complex2(real > 0 ? real : real + mod, fals > 0 ? fals : fals + mod);
}
};
Complex2 quickpow(Complex2 x, LL b, LL mod){
if(b == 0) return Complex2(1, 0);
if(b == 1) return x % mod;
Complex2 l = quickpow(x, b >> 1, mod);
l = (l * l) % mod;
if(b & 1)
l = (l * x) % mod;
return l;
}
bool IsShengyu2(LL x, LL p){
return (::quickpow(x, (p - 1) >> 1, p) == 1) || (x == 0);
}
pair<LL, LL> GetShengyu2(long long n, LL MOD){ // ling : get n 的二次剩余
p = mod = MOD;
if(n == 0) return make_pair(0, 0);
if(IsShengyu2(n, p) == false)
return make_pair(-1, -1); // (-1,-1) 表示无解
srand(time(0));
long long a = rand();
while(IsShengyu2((a * a - n + mod) % mod, p)) a = rand();
Complex2 ans(a, 1);
I = ((a * a - n) % mod + mod) % mod;
ans = quickpow(ans, (p + 1) >> 1, mod);
LL ans1 = ans.real, ans2 = mod - ans.real;
// 理论上n的二次剩余有两个, 有时候会ans1 == ans2
return make_pair(min(ans1, ans2), max(ans1, ans2));
}
}
类欧几里得算法
常用结论:
扩展中国剩余定理
long long exCRT(int n, int mi[], int ai[]){
for(int x, y, i = 2; i <= n; i ++){
long long gcd = exGcd(mi[i], -mi[i - 1], x, y);
long long lcm = abs(mi[i] / gcd * mi[i - 1]);
x = quickmul(x, (ai[i - 1] - ai[i]) / gcd, lcm);
ai[i] = ((quickmul(mi[i], x, lcm) + ai[i]) % lcm + lcm) % lcm;
mi[i] = lcm;
}
return ai[n];
}
扩展Lucas
long long Vp_fact(long long n, long long q){ // 1~n中 质因子p 的个数
long long re = 0;
for(long long i = q; i <= n; i *= q)
re += n / i;
return re;
}
long long Askfact(long long n, long long q, long long mod){// 具体这个函数是干啥的我也不知道
if(n <= q)
return fact[n];
else return Askfact(n / q, q, mod) * quickpow(fact[mod], n / mod, mod) % mod * fact[n % mod] % mod;
// 此处的fact[i] 表示 1~i 中除了q的倍数之外的数的积
}
long long inv(long long x, long long mod){
long long re = 0, k = 0;
exGcd(x, mod, re, k);
return (re % mod + mod) % mod;
}
long long multi_Lucas(long long n, long long m, long long mod, long long q, long long k){
fact[0] = 1;
for(long long i = 1; i <= mod; i ++)
if(i % q == 0)
fact[i] = fact[i - 1];
else fact[i] = fact[i - 1] * i % mod;
long long x = Vp_fact(n, q), y = Vp_fact(m, q), z = Vp_fact(n - m, q);
long long XX = Askfact(n, q, mod), YY = Askfact(m, q, mod), ZZ = Askfact(n - m, q, mod);
return XX * inv(YY, mod) % mod * inv(ZZ, mod) % mod * quickpow(q, x - y - z, mod) % mod;
}// ! ! 这里的inv一定要用exGcd()求解, 因为mod不一定是质数
long long exLucas(long long n, long long m, long long mod){
long long P = mod;
long long q[33], c[33], cnt = 0;
long long mi[33], ai[33];
for(long long i = 2; i * i <= P; i ++)
if(P % i == 0){
q[++ cnt] = i; c[cnt] = 0;
while(P % i == 0)
c[cnt] ++, P /= i;
}
if(P > 1) q[++ cnt] = P, c[cnt] = 1;
for(long long i = 1; i <= cnt; i ++)
mi[i] = quickpow(q[i], c[i]), ai[i] = multi_Lucas(n, m, mi[i], q[i], c[i]);
return exCRT(cnt, mi, ai);
}
威尔逊定理及推论
对于素数p, 有
对于整数n,令表示所有小于等于 n 但不能被 p 整除的正整数的乘积
推论
对于素数 p 和正整数 q 和非负整数 n 有
若存在
升幂定理
记为正整数 n 中 p 的个数, 即 恰好正整除 n
-
p 为奇素数
且 a、b 不是 p 的倍数, -
p = 2
a、 b 为素数
n为奇数时
n为偶数时
扩展BSGS
long long exBSGS(long long a, long long b, long long p){
map<long long ,long long > Map_bsgs;
a %= p, b %= p;
if(b == 1 || p == 1) return 0;
long long cnt = 0, add = 1;
for(long long d = Gcd(a, p); (d ^ 1); d = Gcd(a, p)){
if(b % d) return -1;
b /= d, p /= d, cnt ++;
add = (a / d) * add % p;
if(add == b) return cnt;
}
Map_bsgs.clear();
long long t = ceil(sqrt(p));
long long a_t = 1;
for(long long i = 0; i <= t - 1; i ++, a_t = a_t * a % p)
Map_bsgs[b * a_t % p] = i;
for(long long i = 0, now = add; i <= t; i ++, now = now * a_t % p)
if(Map_bsgs.find(now) != Map_bsgs.end())
if(i * t - Map_bsgs[now] >= 0)
return i * t - Map_bsgs[now] + cnt;
return -1;
}
狄利克雷卷积计算
在差不多线型的复杂度求狄利克雷卷积 (f 和 g 都是数论积性函数)
实际时间消耗大概是线筛的两倍,1.5e7/s
#define f(x)
#define g(x)
g(1) = 1;
f(1) = 1;
for(int i = 2; i <= n; i ++){
if(vis[i] == 0){
lpn[i] = i; prime[++ cnt] = i;
A[i] = (g(i) + f(i)) % mod;
}
for(int j = 1; j <= cnt && prime[j] * i <= n; j ++){
vis[prime[j] * i] = 1;
if(i % prime[j] == 0){
int x = i * prime[j], p = prime[j];
lpn[x] = lpn[i] * p;
if(lpn[x] != i * prime[j])
A[x] = 1ll * A[lpn[x]] * A[x / lpn[x]] % mod;
else{
A[i * prime[j]] = 0;
// 注意 ↓ 这里的 LL 不要写成 int, 如果可以的话全开LL比较方便
for(long long now = 1; now <= lpn[x]; now *= p)
A[i * p] = (A[i * p] + 1ll * f(now) * g(lpn[x] / now)) % mod;
A[i * p] = 1ll * A[i * p] * A[i * p / lpn[x]] % mod;
}
break;
}
else
lpn[i * prime[j]] = prime[j], A[i * prime[j]] = 1ll * A[i] * A[prime[j]] % mod;
}
}
莫比乌斯反演,欧拉反演 与 狄利克雷卷积
莫反结论
欧拉反演
常用数论函数
狄利克雷卷积单位元:
恒等函数:
除数函数:; 当k == 1时,即为除数和 函数;当k==0时,即为除数个数函数
常值函数:
逆元
,若f是积性函数,则也是积性函数
狄利克雷前缀和
复杂度O(n log log n)
(倒)狄利克雷前缀和
b[i] = a[i];// 已知 a 求 b
for(int i = 1; i <= cnt; i ++)
for(int j = 1; j * Prime[i] <= N; j ++)
b[j * Prime[i]] += b[j];
a[i] = b[i]; // 已知 b 求 a
for(int i = cnt; i >= 1; i --)
for(int j = N / Prime[i]; j >= 1; j --)
a[j * Prime[i]] -= a[j];
(倒)狄利克雷后缀和
b[i] = a[i]; // 已知 a 求 b
for(int i = 1; i <= cnt; i ++)
for(int j = N / Prime[i]; j >= 1; j --)
b[j] += b[j * Prime[i]];
a[i] = b[i]; // 已知 b 求 a
for(int i = cnt; i >=1; i --)
for(int j = 1; j * Prime[i] <= N; j ++)
a[j] -= a[j * Prime[i]];
杜教筛
积性函数在组合数处的取值
定义 即 是1~n中质因子p的个数, 记于是有
其中
又因
所以
莫队算法
1.
2.
莫队维护过程中:
每次n,m的变化,对于新的n,m,只需修改n,m,n- m这三个数的质因子的贡献,显然某个数最多有1个所以是的
一阶判别式
1.
2.p2:
因为:
所以:
令
于是的贡献既是
S预处理:每次只对(0 / 1)个p2 修改
PN筛
PN筛求积性函数f的前缀和:
构造一个g函数
1.g是积性函数
2.g的前缀和易求
3.对于质数p,g(p) == f(p)
#include<bits/stdc++.h>
using namespace std;
#define LLL __int128
const LLL mod = 4179340454199820289, W = 5e6;
LLL sum_g[W + 1], prime[W + 1], Inv[111];
bool vis[W + 1];
LLL sum, n, m, T, cnt, tot, tot_PN, ans, inv6, inv2;
int yy;
map<LLL , LLL >Map_sum_g;
vector <LLL > h[W];
LLL exGcd(LLL a, LLL b, LLL &x, LLL &y){
if(b == 0) {x = 1, y = 0; return a; };
LLL d = exGcd(b, a % b, x, y);
LLL z = x; x = y; y = z - y * (a / b);
return d;
}
LLL inv(LLL x, LLL mod){
LLL re = 0, k = 0;
exGcd(x, mod, re, k);
return (re % mod + mod) % mod;
}
void getprime(LLL n){
tot = 0;
for(LLL i = 2; i <= n; i ++){
if(vis[i] == false)
prime[++ tot] = i;
for(LLL j = 1; j <= tot && prime[j] * i <= n; j ++){
vis[i * prime[j]] = true;
if(i % prime[j] == 0) break;
}
}
}
bool ok(LLL x){
return 0 < x && x <= n;
}
LLL Getf(LLL p, LLL c, LLL pc){
return pc * Inv[c] % mod;
}
LLL Getg(LLL p, LLL c, LLL pc){
return pc % mod;
}
LLL GetG(LLL n, LLL d, LLL x){
return x * (x + 1) / 2 % mod;
}
void dfsPN(LLL c, LLL now, LLL hh){
ans = (ans + hh * GetG(n, now, n / now)) % mod;
for(LLL i = c + 1, p = prime[c + 1]; i <= tot && ok(now * p) && ok(now * p * p); i ++, p = prime[i])
for(LLL k = 2, pk = p * p; ok(now * pk); k ++, pk *= p)
dfsPN(i, now * pk, hh * h[p][k] % mod);
}
inline LLL quickpow(LLL x, long long b, LLL mod){
LLL j = 1;
;
for(; b; b >>= 1, x = x * x % mod)
if(b & 1)
j = j * x % mod;
return j % mod;
}
void work(){
long long t;
cin >> t;
n = t;
ans = 0;
dfsPN(0, 1, 1);
long long ANS = ans * quickpow(n, mod - 2, mod) % mod % mod;
cout << ANS << endl;
}
signed main(){
n = 1e12 + 1;
Inv[1] = 1;
for(int i = 2; i <= 100; i ++)
Inv[i] = (mod - mod / i) * Inv[mod % i] % mod;
inv6 = inv(6, mod);
inv2 = inv(2, mod);
getprime(W - 1);
for(LLL u = 1; u <= tot && prime[u] * prime[u] <= n; u ++){
LLL p = prime[u];
h[p].push_back(1);
for(LLL c = 1, p_c = p; p_c <= n; p_c *= p, c ++){
h[p].push_back(Getf(p, c, p_c));
for(LLL i = 1, p_i = p; i <= c; i ++, p_i *= p)
h[p][c] = ((h[p][c] - Getg(p, i, p_i) * h[p][c - i]) % mod + mod) % mod;
}
}
int T = 1;
cin >> T;
while(T --) work();
}
min25筛
#include<bits/stdc++.h>
using namespace std;
#define int long long
int quickpow(int x, int b, int mod);
const int N = 2e6+100, mod = 1e9+7, inv6 = quickpow(6, mod - 2, mod), inv2 = quickpow(2, mod - 2, mod);
int Q, tot_prime, n, sw, ans = 0, lenth = 2, w[N];
int g[11][N], id1[N], id2[N], gp[11][N], prime[N], vis[N], K[11], c[11], G[N], F_prime[N];
int kk;
inline int getid(int x){
return (x <= Q ? id1[x] : id2[n / x]);
}
int quickpow(int x, int b, int mod){
if(b == 0) return 1;
if(b == 1) return x % mod;
int l = quickpow(x, b >> 1, mod);
l = l * l % mod;
if(b & 1) return l * x % mod;
else return l;
}
inline int f_p(int p){//真实的f(p)的表达式
// 注意取模
p %= mod;
return p * (p - 1) % mod;
}
int f_p_k(int p_k, int p, int k){//真实的f(p_k)的表达式
// 注意取模
p_k %= mod;
if(k == 1) return f_p(p);
else return p_k * (p_k - 1) % mod;
}
int S(int x, int y){
// S(x, y) : 1 ~ x 中 minp > prime[y]的数字的多项式的和
if(prime[y] >= x) return 0;
int p = getid(x);
int re = (G[p] - F_prime[y] + mod) % mod;
for(int i = y + 1; i <= tot_prime && prime[i] * prime[i] <= x; i ++){
int p_k = prime[i];
for(int k = 1; p_k <= x; k ++, p_k *= prime[i]){
int o = p_k % mod;
int add = 0;
add = f_p_k(p_k, prime[i], k);
re = (re + add * (S(x / p_k, i) + (k != 1))) % mod;
}
}
return re;
}
int sum_mi(int n, int k, int mod){
if(k == 0) return n % mod;
else if(k == 1) return(n + 1) * n % mod * inv2 % mod;
else if(k == 2) return(n + 1) * n % mod * (2 * n + 1) % mod * inv6 % mod;
else if(k == 3) return quickpow(n % mod * (n + 1) % mod * inv2 % mod, 2, mod);
}
void work(){
int ans = 0;
cin >> n;
Q = sqrt(n);
lenth = 2;
c[1] = 2, K[1] = 1;
c[2] = 1, K[2] = -1;
//这里的是f(p)(不是f(p_k))的多项式,但只用于预处理,与真实的f(p)无直接关系
{// clear
tot_prime = sw = 0;
for(int i = 1; i <= Q; i ++)
vis[i] = 0;
for(int i = 1; i <= 3 * Q; i ++){
for(int j = 1; j <= lenth; j ++)
g[j][i] = 0;
G[i] = 0;
}
}
prime[0] = 1;
for(int i = 2; i <= Q; i ++){
if(!vis[i]) prime[++ tot_prime] = i;
for(int j = 1; j <= tot_prime && i * prime[j] <= Q; j ++){
vis[i * prime[j]] = 1;
if(i % prime[j] == 0) break;
}
}
for(int i = 1; i <= tot_prime; i ++)
for(int j = 1; j <= lenth; j ++)
gp[j][i] = (gp[j][i - 1] + quickpow(prime[i], c[j], mod)) % mod;
// gp[j][i] 表示prime[1] ~ prime[i] 的单项式的和 i <= tot_prime (prime <= sqrt(n))
for(int l = 1, r; l <= n; l = r + 1){
r = n / (n / l);
w[++ sw] = n / r;
int x = w[sw] % mod;
for(int i = 1; i <= lenth; i ++)
g[i][sw] = (sum_mi(x, c[i], mod) - 1 + mod) % mod;
if(n / r <= Q)
id1[n / r] = sw;
else id2[r] = sw;
}
/*
查询1 ~ n / x的单项式的和 --> g[j][sw]
if(n / x <= Q) : sw = id1[n / x];
if(n / x > Q) : sw = id2[x];
w[sw] = n / x
y = n / x
查询1 ~ y的单项式的和 --> g[j][getid(y)]
w[getid(y)] = y
*/
for(int i = 1; i <= tot_prime; i ++)
for(int j = 1, squ = prime[i] * prime[i]; j <= sw && w[j] >= squ; j ++){
int p = w[j] / prime[i];
p = getid(p);
for(int v = 1; v <= lenth; v ++)
g[v][j] = (g[v][j] - quickpow(prime[i], c[v], mod) * (g[v][p] - gp[v][i - 1] + mod) % mod + mod) % mod;
}
// 查询1 ~ n / x的所有 质数 的单项式的和 --> g[j][sw]
for(int j = 1; j <= sw; j ++)
for(int i = 1; i <= lenth; i ++)
G[j] = (G[j] + K[i] * g[i][j]) % mod;
// 询1 ~ n / x的所有 质数 的多项式的和 --> G[sw]
/*
for(int j = 1; j <= sw - 1; j ++)
G[j] = (G[j] + 2) % mod;
这里是G[n / x]中f(p)由多项式之和 向 真实表达式之和 的转化
*/
for(int i = 1; i <= tot_prime; i ++)
F_prime[i] = (F_prime[i - 1] + f_p(prime[i])) % mod;
//F_prime[i] 小于等于prime[i]的质数的真实表达式之和 , prime[i] <= Q
cout << ((S(n, 0) + 1) % mod + mod) % mod;
return ;
}
signed main(){
work();
}
// 100000000000
Miller Rabin素数测试 & Pollard Rho因数分解
namespace Fctr{
#define int long long
set<int > Prime;
mt19937 rnd(random_device{}());
int qmul(int x, int y, int p){
int r = x * y - p * (int)(1.L / p * x * y);
return r - p * (r >= p) + p * (r < 0);
}
int quickpow(int x, int y, int p){
int r = 1;
for(; y; y >>= 1, x = qmul(x, x, p))
if(y & 1) r = qmul(r, x, p);
return r;
}
bool MR(int n){
if(n < 4) return n > 1;
int k = __builtin_ctz(n - 1), m = n - 1 >> k;
for(int i = 0; i <= 4; i ++){
int e = quickpow(rnd() % (n - 3) + 2, m, n), j = k;
for(; e != 1 && e != n - 1 && j --; e = qmul(e, e, n));
if(e != n - 1 && j != k)
return false;
}
return true;
}
int f(int x, int n){
return qmul(x, x, n) + 1;
}
int PR(int n){
int p = 2, q;
for(int x = 0, y = 0, i = 0; i ++ % 255 || __gcd(p, n) == 1;){ // !!!
x = f(x, n), y = f(f(y, n), n);
if(x == y){
x = rnd() % n;
y = f(x, n);
}
q = qmul(p,abs(x-y),n);
if(q)p=q;
}
return __gcd(p,n);
}
void fctr(int n){
if(MR(n)){
Prime.insert(n);
return ;
}
int e = PR(n);
fctr(n / e);
fctr(e);
}
}
*2022/10/5 新增积性函数在组合数处的取值
*2022/10/16 修改Miller Rabin素数测试 & Pollard Rho因数分解模板,速度大幅度优化
*2022/11/24 新增了莫比乌斯反演和欧拉反演的诸多公式
*2022/11/24 改进了min_25筛模板
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)