【模板】数论板子
最大公约数、最小公倍数
关于 ex_gcd :
原式:
递归后的式子:
由于:
所以:
结果:
边界是 \(b = 0\) 时, \(\gcd(a,b) = a\) , \(x = 1,y = 0\) 。
template<typename Tec>
Tec gcd(Tec a,Tec b) {
return b ? gcd(b,a%b) : a;
}
template<typename Tec>
Tec lcm(Tec a,Tec b) {
return a/gcd(a,b)*b;
}
template<typename Tec>
Tec exgcd(Tec a,Tec b,Tec &x,Tec &y) {
if(!b) {
x = 1;
y = 0;
return a;
}
Tec receive = exgcd(b,a%b,y,x);
y = y-(a/b)*x;
return receive;
}
数论函数
欧拉函数
template<typename Tec>
Tec phi(Tec x) {
Tec sqt = sqrt(x);
Tec res = x;
for(int i = 2;i <= sqt;++i)
if(x%i == 0) {
res = res/i*(i-1);
x /= i;
while(x%i == 0)
x /= i;
}
if(x > 1)
res = res/x*(x-1);
return res;
}
数论分块
用于求解
亦可求解多维
前提是求出了数论函数\(f(n)\)的前缀和。
ull NTSD(ull pre[],int n) {
//number-theory;
//sqrt-decomposition;
int l = 1, r = 0;
ull result = 0;
while(l <= n) {
r = (int)floor(n/floor(1.0*n/l));
result += (pre[r]-pre[l-1])*1ll*floor(n/l);
l = r+1;
}
return result;
}
ull NTSDQ(ull pre[],int n,int m) {
int l = 1, r = 0;
ull result = 0;
while(l <= min(m,n)) {
r = (int)min(n/(1.0*n/l),m/(1.0*m/l));
result += (pre[r]-pre[l-1])*1ll*(n/l)*(m/l);
l = r+1;
}
return result;
}
筛法
\(prime\)
const int N = 1e7+10;
int prime[N];
bool notprime[N];
int minprime[N];
void prime_sieve(const int maxn) {
register int i, multi_helper;
register int* j;
const int half_maxn = maxn>>1;
for(i = 2;i <= half_maxn;++i) {
if(!notprime[i]) {
prime[++prime[0]] = i;
minprime[i] = i;
}
for(j = prime+1;*j&&*j <= minprime[i];++j) {
notprime[i*(*j)] = true;
minprime[i*(*j)] = *j;
}
}
for(;i <= maxn;++i)
if(!notprime[i]) {
prime[++prime[0]] = i;
minprime[i] = i;
}
}
const int N = 1e8+5e7+10;
const int P = 1e7+10;
int prime[P];
int minprime[N];
void prime_sieve(const int maxn) {
register int i, upd;
register int* j;
const int half_maxn = maxn>>1;
for(i = 2;i <= half_maxn;++i) {
if(!minprime[i]) {
prime[++prime[0]] = i;
minprime[i] = i;
}
upd = std :: min(maxn/i,minprime[i]);
for(j = prime+1;*j&&*j <= upd;++j)
minprime[i*(*j)] = *j;
}
for(;i <= maxn;++i)
if(!minprime[i])
prime[++prime[0]] = i;
}
这个常数还是比较小的。
可以在 \(80\ ms\) 内处理出 \(1e7\) 以内的素数。
可以在 \(0.8\ s\) 内处理出 \(1e8\) 以内的素数。
\(\varphi(n)\)
ull phi[z];
void line_phi(int n) {
b.reset();
phi[1] = 1;
for(int i = 2;i <= n;++i) {
if(!b[i]) {
prime[++prime[0]] = i;
phi[i] = i-1;
}
for(int j = 1;j <= prime[0];++j) {
if(i*prime[j] > n) break;
if(i%prime[j])
phi[i*prime[j]] = phi[i]*phi[prime[j]];
else {
phi[i*prime[j]] = phi[i]*prime[j];
break;
}
}
}
}
\(\mu(n)\)
int mu[z];
void line_mu(int n) {
b.reset();
mu[1] = 1;
for(int i = 2;i <= n;++i) {
if(!b[i]) {
mu[i] = -1;
prime[++prime[0]] = i;
}
for(int j = 1;j <= prime[0];++j) {
if(i*j > n) break;
b[i*prime[j]] = 1;
if(i%prime[j] == 0) {
mu[i*prime[j]] = 0;
break;
}
mu[i*prime[j]] = -mu[i];
}
}
}
开始卡常。
const int N = 1e7+10;
const int P = 4e6+10;
int mu[N];
int minprime[N];
int prime[N];
void init(int init_size) {
const int half_size = init_size>>1;
register int i, *j, upd, m_h;
register int *p = prime+1;
mu[1] = 1;
for(i = 2;i <= half_size;++i) {
if(!minprime[i]) {
minprime[i] = i;
*p++ = i;
mu[i] = -1;
}
if((upd = init_size/i) >= minprime[i]) {
for(j = prime+1;*j < minprime[i];++j) {
minprime[m_h = i*(*j)] = *j;
mu[m_h] = -mu[i];
}
minprime[m_h = i*(*j)] = *j;
mu[m_h] = 0;
} else {
for(j = prime+1;*j <= upd;++j) {
minprime[m_h = i*(*j)] = *j;
mu[m_h] = -mu[i];
}
}
mu[i] += mu[i-1];
}
for(;i <= init_size;++i)
if(!minprime[i])
mu[i] = mu[i-1]-1;
else
mu[i] += mu[i-1];
}
可以稳定在 \(128\,ms\) 左右,不错了。
注意卡常的板子筛的是 \(mu(n)\) 的前缀和。
\(\tau(n)\)
ull tau[z];
ull num[z];
void line_tau(int n) {
tau[1] = 1;
for(int i = 2;i <= n;++i) {
if(!b[i]) {
b[i] = 1;
prime[++prime[0]] = i;
tau[i] = 2;
num[i] = 1;
}
for(int j = 1;j <= prime[0];++j) {
if(i*prime[j] > n) break;
b[i*prime[j]] = 1;
if(i%prime[j] == 0) {
num[i*prime[j]] = num[i]+1;
tau[i*prime[j]] = tau[i]/num[i*prime[j]]*(num[i*prime[j]]+1);
break;
} else {
num[i*prime[j]] = 1;
tau[i*prime[j]] = tau[i]*2;
}
}
}
}
\(\sigma(n)\)
ull sigma[z];
ull g[z];
void line_sigma(int n) {
sigma[1] = g[1] = 1;
for(int i = 2;i <= n;++i) {
if(!b[i]) {
b[i] = 1;
prime[++prime[0]] = i;
g[i] = i+1;
sigma[i] = i+1;
}
for(int j = 1;j <= prime[0];++i) {
if(i*prime[j] > n) break;
b[i*prime[j]] = 1;
if(i%prime[j] == 0) {
g[i*prime[j]] = g[i]*prime[j]+1;
sigma[i*prime[j]] = sigma[i]/g[i]*g[i*prime[j]];
break;
} else {
sigma[i*prime[j]] = sigma[i]*sigma[prime[j]];
g[i*prime[j]] = 1+prime[j];
}
}
}
}
拉格朗日插值
由同余式:
得方程组:
因此:
code:
#include <stdio.h>
#define ull long long
const int z = 2048;
const ull moyn = 998244353;
int n;
ull k, x[z], y[z];
ull fu, fd;
ull ans;
ull quick_pow(ull a,ull n,const ull p = moyn) {
ull res = 1ll;
while(n) {
if(n&1) res = res*a%p;
a = a*a%p;
n >>= 1;
}
return res;
}
ull inverse(ull a,const ull p = moyn) {
return quick_pow(a,p-2);
}
void Lagrange() {
scanf("%d %lld",&n,&k);
for(int i = 1;i <= n;++i)
scanf("%lld %lld",&x[i],&y[i]);
for(int i = 1;i <= n;++i) {
fu = y[i];
fd = 1ll;
for(int j = 1;j <= n;++j)
if(i != j) {
fu = fu*(k-x[j])%moyn;
fd = fd*(x[i]-x[j])%moyn;
}
ans += fu*inverse(fd)%moyn;
}
printf("%lld\n",(ans%moyn+moyn)%moyn);
}
signed main() {
Lagrange();
}
逆元
单个逆元
- \(Fermat\)小定理求法
\(\because a^{p-1}\equiv 1\pmod p\) p为质数
\(\therefore a^{p-2}\equiv \dfrac{1}{a}\pmod p\)
template<typename Tec>
Tec quick_pow(Tec _a,Tec _n,Tec _p) {
Tec _res = 1;
while(_n) {
if(_n&1) _res = _res*_a%_p;
_a = _a*_a%_p;
_n >>= 1;
}
return _res;
}
template<typename Tec>
Tec inv(Tec _a,Tec _p) {
return quick_pow(_a,_p-2,_p);
}
- \(Euler\)定理求法
\(\because a^{\varphi(p)-1}\equiv 1\pmod p\)
\(\therefore a^{\varphi(p)-2}\equiv \dfrac{1}{a}\pmod p\)
- 扩展欧几里得求法
template<typename Tec>
Tec ex_gcd(Tec a,Tec b,Tec &x,Tec &y) {
if(!b) {
x = 1, y = 0;
return a;
}
Tec res = ex_gcd(b,a%b,x,y);
Tec tmp = x;
x = y;
y = tmp-a/b*y;
return res;
}
template<typename Tec>
Tec gcd_inv(Tec a,Tec p) {
Tec x, y;
Tec tmp = ex_gcd(a,p,x,y);
if(tmp != 1) return -1;
return ((x%p)+p)%p;
}
线性逆元求法
- 线性求逆元
\(\text{设}p = k\times i+l(p,k,i,l \in \mathbb{N})\)
\(\therefore i = \dfrac{p-l}{k}\)
\(\because \dfrac{1}{l}\equiv inv_{l}\pmod p\)
\(\because \dfrac{1}{i}\equiv inv_i\pmod p\)
\(\therefore inv_i\equiv inv_l\times(p-k)\pmod p\)
\(\therefore inv_i \equiv inv_{p\mod i}\times(p-\dfrac{p}{i})\pmod p\)
long long inv[1024];
void line_inv(int n,long long p) {
inv[1] = inv[0] = 1;
for(int i = 2;i <= n;++i)
inv[i] = inv[p%i]*(p-p/i)%p;
}
- 阶乘求逆元
\(\because \dfrac{1}{(i+1)!}\equiv finv_{i+1}\pmod p\)
\(\because \dfrac{1}{i!}\equiv finv_i\pmod p\)
\(\therefore inv_i\equiv inv_{i+1}\times(i+1)\pmod p\)
long long fact[1024], inv[1024];
void line_finv(int n,long long p) {
fact[0] = 1;
for(int i = 1;i <= n;++i)
fact[i] = fact[i-1]*i%p;
inv[n] = sinv(fact[n],p);
for(int i = n-1;i >= 1;--i)
inv[i] = inv[i+1]*(i+1)%p;
}
正推也不是不行对吧
long long fact[1024], inv[1024];
void line_sinv(int n,long long p) {
fact[0] = inv[0] = 1;
for(int i = 1;i <= n;++i)
inv[i] = (p-p/i)*inv[p%i]%p;
for(int i = 1;i <= n;++i) {
inv[i] = inv[i-1]*inv[i]%p;
fact[i] = fact[i-1]*i%p;
}
}