【模板】数论板子

最大公约数、最小公倍数

关于 ex_gcd :

原式:

\[a\times x_1+b\times y_1 = \gcd(a,b) \]

递归后的式子:

\[b\times x_2+(a\bmod b)\times y_2 = \gcd(b,a\bmod b) \]

由于:

\[\gcd(a,b) = \gcd(b,a\bmod b) \]

所以:

\[\begin{aligned} a\times x_1+b\times y_1 &= b\times x_2+(a\bmod b)\times y_2\\ &= a\times x_1+b\times y_1 = b\times x_2+(a-\left\lfloor\frac{a}{b}\right\rfloor\times b)\times y_2\\ &= a\times y_2+b\times(x_2-\left\lfloor\frac{a}{b}\right\rfloor\times y_2) \end{aligned}\]

结果:

\[\begin{cases}x_1 = y_2\\y_1 = x_2-\left\lfloor\frac{a}{b}\right\rfloor\times y_2\end{cases} \]

边界是 \(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;
}

数论分块

用于求解

\[\sum\limits_{i=1}^{n}f_i\cdot \left\lfloor\dfrac{n}{i}\right\rfloor \]

亦可求解多维

\[\sum\limits_{i=1}^{\min(n_1,n_2,\cdots,n_k)}(f_i\cdot \prod\limits_{j=1}^{k}\left\lfloor\dfrac{n_j}{i}\right\rfloor) \]

前提是求出了数论函数\(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];
			}
		}
	}
}

拉格朗日插值

由同余式:

\[\large f(x)\equiv f(y)\pmod{x-y} \]

得方程组:

\[\large \begin{cases} f(x)\equiv y_1\pmod{x-x_1}\\ f(x)\equiv y_2\pmod{x-x_2}\\ \vdots\\ f(x)\equiv y_n\pmod{x-x_n}\\ \end{cases}\]

因此:

\[\large f(x)=\sum\limits_{i=1}^{n}y_i\times\sum\limits_{j\not=i}^{}\dfrac{x-x_j}{x_j-x_i} \]

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();
}

逆元

单个逆元

  1. \(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);
}
  1. \(Euler\)定理求法

\(\because a^{\varphi(p)-1}\equiv 1\pmod p\)

\(\therefore a^{\varphi(p)-2}\equiv \dfrac{1}{a}\pmod p\)

  1. 扩展欧几里得求法
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;
}

线性逆元求法

  1. 线性求逆元

\(\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;
}
  1. 阶乘求逆元

\(\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;
	}
}
posted @ 2022-06-07 21:13  bikuhiku  阅读(21)  评论(0编辑  收藏  举报