数论杂谈
数论杂谈
欧拉函数
::::info[定义]{open}
欧拉函数,即
性质
-
欧拉函数是
{\color{red}积性函数}
::::info[积性函数的定义] 在数论中,若函数f(n) 满足f(1)=1 ,且f(xy)=f(x)f(y) 对任意互质的x, y \in\mathbf{N}^* 都成立,则f(n) 为 积性函数. :::: 即对\gcd(a,b)=1 时\varphi(ab)=\varphi(a) \times \varphi(b) -
[证明](https://oi-wiki.org/math/number-theory/euler-totient/#%E6%80%A7%E8%B4%A8)需利用[莫比乌斯反演](https://oi-wiki.org/math/number-theory/mobius/)~~我不会~~ -
若
n = p^k ,其中p 是质数,那么\varphi(n) = p^k - p^{k - 1} . (随便推推就可以推出来) ::::info[证明] 显然对于从 1 到p^k 的所有数中,除了p^{k-1} 个p 的倍数以外其它数都与p^k 互素,故\varphi(p^k)=p^k-p^{k-1}=p^{k-1}\times(p-1) ,证毕. :::: -
由唯一分解定理,设
n = \prod_{i=1}^{s}p_i^{k_i} ,其中p_i 是质数,有\varphi(n) = n \times \prod_{i = 1}^s{\dfrac{p_i - 1}{p_i}} . ::::info[证明]{open}\varphi(n) &= \prod_{i=1}^{s}\varphi(p_i^{k_i}) \\ &= \prod_{i=1}^{s}(p_i-1) \times p_i^{k_i-1} \\ &= \prod_{i=1}^{s}\frac{p_i-1}{p_i} \times p_i^{k_i} \\ &= n \times \prod_{i=1}^{s}\frac{p_i-1}{p_i} \end{aligned} ::::
求欧拉函数
单个欧拉函数
如果只求单个欧拉函数,完全只需要依照质因数分解来求就可以了就可以了.
::::info[引理]
设
#include <cmath>
int euler_phi(int n) {
int ans = n;
for (int i = 2; i * i <= n; i++)
if (n % i == 0) {
ans = ans / i * (i - 1);
while (n % i == 0) n /= i;
}
if (n > 1) ans = ans / n * (n - 1);
return ans;
}
::::
线性求多个欧拉函数
但是会发现如果直接求多个欧拉函数都用单个欧拉函数求法,时间复杂度就是
显而易见,你会发现线性筛有一个非常好的性质,每个数都会被它最小的质因数筛掉.
for(int i=2;i<=m;i++){
if(!b[i]) prime[++p]=i;
for(int j=1;j<=p&&i*prime[j]<=m;j++){
b[i*prime[j]]=1;
if(i%prime[j]==0) break;
}
}
那么分别考虑
当
很明显
当
那么我们就知道如何用线性筛来求多个欧拉函数了.
::::success[代码]
phi[1]=1;
for(int i=2;i<=n;i++){
if(!b[i]){
phi[i]=i-1;
sum++;
p[sum]=i;
}
for(int j=1;j<=sum;j++){
if(p[j]*1ll*i>n) break;
b[i*p[j]]=1;
if(i%p[j]==0){//当整除时
phi[i*p[j]]=p[j]*phi[i];
continue;
}
phi[i*p[j]]=(p[j]-1)*phi[i];//当不整除时
}
}
::::
P2398 GCD SUM
首先我们看到这道题时,应该会想到一个朴素的想法,就是把求
由于
你会发现一个很神奇的事
#include<bits/stdc++.h>
using namespace std;
int n,p[20005],phi[100005],sum;
long long q[100005];
long long ans;
bool b[100005];
int main(){
cin>>n;
phi[1]=1;
for(int i=2;i<=n;i++){
if(!b[i]){
phi[i]=i-1;
sum++;
p[sum]=i;
}
for(int j=1;j<=sum;j++){
if(p[j]*1ll*i>n) break;
b[i*p[j]]=1;
if(i%p[j]==0){
phi[i*p[j]]=p[j]*phi[i];
continue;
}
phi[i*p[j]]=(p[j]-1)*phi[i];
}
}
q[2]=phi[2];
for(int i=3;i<=n;i++){
q[i]=q[i-1]+phi[i];//把j=1的情况排除
}
for(int i=1;i<=n;i++){
ans+=q[n/i]*i;
}
ans*=2;
ans+=(1+n)*1ll*n/2;//加上i=j的情况
cout<<ans;
return 0;
}
::::
Lucas
::::info[定理]{open}
对于素数
| 其中,当 |
|---|
| ::::info[证明]{open} |
| 首先我们知道 |
| $$ |
| (1+x)^p \equiv (1+x^p) \pmod p |
| $$ |
| :::info[证明]{open} |
| $$ |
| (1+x)^p= \prod_{i=0}^{p} {p \choose i} x^i |
| $$ |
| 那么很明显当 |
| 于是只有 |
| 那么 |
| $$ |
| (1+x)^p \equiv (1+x^p) \pmod p |
| $$ |
| ::: |
| 设 |
| 那么 |
| $ |
| \begin{aligned} |
| (1+x)^n &=(1+x)^{cp}(1+x)^d \ |
| &=(1+x^p)^c(1+x)^d \pmod p |
| \end{aligned} |
| $ |
| 考虑 |
| $ |
| \begin{aligned} |
| {n \choose m}x^m &\equiv {c \choose a} x^{ap}{d \choose b}x^b \pmod p\ |
| {n \choose m}x^m &\equiv {c \choose a}{d \choose b}x^{ap+b} \pmod p\ |
| {n \choose m}x^m &\equiv {c \choose a}{d \choose b}x^m \pmod p\ |
| {n \choose m} &\equiv {c \choose a}{d \choose b} \pmod p \ |
| {n \choose m} &\equiv {\lfloor \frac{n}{p} \rfloor \choose \lfloor \frac{m}{p} \rfloor}{n \bmod p \choose m \bmod p} \pmod p |
| \end{aligned} |
| $ |
| 得证 |
| :::: |
| 这个定理可以在组合数取模数并不是很大的情况下使用. |
P3773 [CTSC2017] 吉夫特
一眼发现是lucas板子题.
使
那么
因为只在
所以可以说只能
这很明显直接用枚举子集
#include<bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
int n,a[211987];
int dp[233334],ans;
inline int read() {
int x=0,f=1;
char ch=getchar_unlocked();
while(ch<'0'||ch>'9') {
if(ch=='-')
f=-1;
ch=getchar_unlocked();
}
while(ch>='0'&&ch<='9')
x=x*10+ch-'0',ch=getchar_unlocked();
return x*f;
}
int main(){
n=read();
for(register int i=1;i<=n;++i){
a[i]=read();
}
for(register int i=1;i<=n;++i){
for(register int j=a[i]&(a[i]-1);j;j=(j-1)&a[i]){//子集枚举
dp[j]+=dp[a[i]]+1;
dp[j]%=mod;
}
ans+=dp[a[i]];
ans%=mod;
}
printf("%d",ans);
}
::::
扩展欧几里得
这种算法常用于求
::::info[证明]{open} 本证明属于构造法
不妨设:
由 欧几里得定理 可知
又因为
所以
因为
::::
函数返回的值为
long long exgcd(long long a, long long b, long long &x, long long &y) {
if (!b) {
x = 1, y = 0;
return a;
}
int d = exgcd(b, a % b, y, x);
y -= a / b * x;
return d;
}
::::info[最小
∵
∴
∴
∴对于任意一个
| ::: |
|---|
逆元
::::info[定义]{open}
对于非零整数
快速幂法
::::info[回顾一下费马小定理]{open}
若
那么可推得 :::align{center}
:::
很明显求
long long poww(long long x,int y,int p){//快速幂
int ans=1;
while(y){
if(y&1!=0){
ans=(ans*x)%p;
}
x*=x;
x%=p;
y>>=1;
}
return ans;
}
long long ny(int a,int p){//逆元
return poww(a,p-2,p);
}
::::
exgcd法
很容易发现逆元其实是一个特殊的扩展欧几里得. ::::info[推导]{open} :::align{center}
| ::: |
|---|
| 这种算法不需要考虑 |
| ::::success[代码] |
| ```cpp |
| long long exgcd(long long a, long long b, long long &x, long long &y) {//扩展欧几里得 |
| if (!b) { |
| x = 1, y = 0; |
| return a; |
| } |
| long long d = exgcd(b, a % b, y, x); |
| y -= a / b * x; |
| return d; |
| } |
| long long ny(long long a){//逆元 |
| long long d=exgcd(a,(long long)mod,x,y); |
| return (x%p+p)%p; |
| } |
| ``` |
| :::: |
线性求法
当遇到需要连续求
| ::: |
|---|
| 这样我们只需要从 |
::::success[代码]
ny[1]=1;//逆元数组
cout<<1<<'\n';
for(int i=2;i<=n;i++){
ny[i]=(long long)(p-p/i)*ny[p%i]%p;
cout<<ny[i]<<'\n';
}
::::
### 另一种多个元素的求法
有些会要求快速求出数 $a_1,a_2,\cdots,a_n$ 在模 $m$ 意义下的逆元,保证 $m$ 和 $a$ 里面所有数互素.
::::info[证明]{open}
考虑序列 $\{a_i\}$ 的前缀积:
:::align{center}
$S_0 = 1,~ S_i = a_iS_{i-1},~ i=1,2,\cdots,n$
:::
因 $a$ 里面所有数都与 $m$ 互质,那么 $a$ 的前缀积也与 $m$ 互素。
我们知道求整体乘积的逆元等于所有单个逆元的乘积:
:::align{center}
$S_n^{-1} \times S_n \equiv 1 \pmod m$
$(a_1\times a_2\times \cdots \times a_n)^{-1}*(a_1\times a_2\times \cdots \times a_n) \equiv 1 \pmod m$
:::
那么我们每次只需要用 $S_i^{-1} \times a_i$ 就可以把 $a_i^{-1}$ 消掉然后得到 $S_{i-1}^{-1}$,从而推出所有的前缀积的逆元.
然后用 $S_{i-1} \times S_i^{-1}$ 就可以把 $S_{i-1}$ 的逆元消掉,只留下 $a_i$ 的逆元.
由此就可以求出所有的逆元.
::::
这样因为只求了一次逆元,时间复杂度为度是 $O(n+\log m)$ .
::::success[代码]
```cpp
q[0]=1;
q[1]=1;
for(int i=2;i<=p-1;i++){//求前缀和
q[i]=q[i-1]*i%p;
}
qny[n]=poww(q[n],p-2);
for(int i=n-1;i>=0;i--){//求前缀积逆元
qny[i]=qny[i+1]*(i+1)%p;
}
for(int i=1;i<=n;i++){//求单个数逆元
ny[i]=qny[i]*q[i-1];
}
::::
容斥
好(屎)题?
AT_abc465_f Sjeltzer?
P3813 [FJOI2017] 矩阵填数
P1316 Mivik 写书
附录
数论分块
再推式子时,如果遇到了一个形如
想法是直接考虑有多少个不同的
注意到由于
那么考虑知道
我们知道
代码如下:
inline int Calc(int n){
int Ans=0;
for(int l=1,r;l<=n;l=r+1){
r=n/(n/l);
Ans+=(SF(r)-SF(l-1))*g(n/l);
}
return Ans;
}
如果要考虑给更高维的数论分块,相当于把
P2260 [清华集训 2012] 模积和
先钦定
考虑到
::::success[代码]
#include<bits/stdc++.h>
using namespace std;
const int mod=19940417;
long long sum1,sum2,ans;
long long n,m;
int main(){
cin>>n>>m;
if(n>m)
swap(n,m);
sum1=n*1ll*n%mod;
sum1%=mod;
for(long long l=1,r;l<=n;l=r+1){//一维数论分块
r=n/(n/l);
sum1+=-1ll*(r+l)*(r-l+1)/2%mod*(n/r)%mod;
sum1%=mod;
}
sum1=(sum1+mod)%mod;
sum2=m*1ll*m%mod;
sum2%=mod;
for(long long l=1,r;l<=m;l=r+1){//把两个一维数论分块分开求
r=m/(m/l);
sum2+=-1ll*(r+l)*(r-l+1)/2%mod*(m/l)%mod;
sum2%=mod;
}
sum2=(sum2+mod)%mod;
ans=sum1*sum2%mod-n*1ll*m%mod*n%mod;
ans=(ans+mod)%mod;
for(long long l=1,r;l<=n;l=r+1){//二位数论分块
r=min(n/(n/l),(m/(m/l)));
ans+=(r+l)*1ll*(r-l+1)/2%mod*(n*1ll*(m/r)%mod+m*1ll*(n/r)%mod)%mod;
ans%=mod;
ans-=(r*1ll*(r+1)%mod*(2*r+1)%mod*3323403%mod-(l-1)*1ll*l%mod*(2*l-1)%mod*3323403%mod+mod)%mod*(m/l)%mod*(n/l)%mod;
ans%=mod;
ans=(ans+mod)%mod;
}
ans=(ans+mod)%mod;
cout<<ans;
return 0;
}
::::