数论浅略总结
数论总结
前记:
由于是数论总结 所以肯定有地方是不够严谨/根本没证
(靠 怎么没换行 不管了)
CRT与扩展CRT
CRT:
求解线性一元同余方程组: 满足\(m\)互质
求解方法:
中国剩余定理(CRT)及其扩展(EXCRT)详解 - ailanxier - 博客园 (cnblogs.com)
(太懒了)
扩展CRT:
问题同上 但是\(m\)不互质
我们将取模去掉 得到:
联立得:\(m1k1=c2-c1+m2k2\)
当且仅当\((m1,m2)|(c2-c1)\)时有解
令\(d=(m1,m2)\) 使得等式两边同时除以\(d\) 得:
\(\frac{m1}{d}k1=\frac{c2-c1}{d}+\frac{m2}{d}k2\)
同时取模\(\frac{m2}{d}\) 得:
\(\frac{m1}{d}k1 \equiv \frac{c2-c1}{d} \ mod(\ \frac{m2}{d})\)
将\(\frac{m1}{d}\)移项至右边 得:
\(k1=inv(\frac{m1}{d},\frac{m2}{d}) * \ \frac{c2-c1}{d}+ y\frac{m2}{d}\)
将\(k1\)带入原式(\(x=c1+m1k1\))中 得:
\(x \ \equiv \ inv(\frac{m1}{d},\frac{m2}{d})\ * \ \frac{c2-c1}{d}\ *\ m1+c1\ mod(\ \frac{m1m2}{d})\)
发现所有量都变成了已知量 直接求解即可 具体而言 有\(n\)条式子就需要进行\(n-1\)次
int gcd(int x,int y){
if(y==0)return x;
return gcd(y,x%y);
}
int lcm(int x,int y){
return x/gcd(x,y)*y;
}
int exgcd(int x,int y,int &s1,int &s2){
if(y==0){
s1=1,s2=0;
return x;
}
int d=exgcd(y,x%y,s1,s2);
int temp=s1;
s1=s2,s2=temp-x/y*s2;
return d;
}
int mul(int x,int p,int mod){
if(p<0)x=-x,p=-p;
int val=0;
for(int i=p;i;i/=2){
if(i&1)val=(val+x)%mod;
x=(x+x)%mod;
}
return val;
}
int solve(){
int R=r[1],M=a[1];
for(int i=2;i<=k;i++){
int lst_m=M,now_m=a[i],c=r[i]-R,x,y;
int d=exgcd(lst_m,now_m,x,y);
if(c%d)return -1;
lst_m/=d,now_m/=d,c/=d;
x=((mul(x,c,now_m)+now_m)%now_m)%now_m;
R=R+x*M;M=M*a[i]/d;R=(R+M)%M;
}
R=(R+M-1)%M+1;
if(R+k-1>m)return -1;
return R;
}
(从这以后的'*'全都代表卷积 懒的改了)
欧拉函数:
定义 \(\varphi\) 为欧拉函数 其计算方式为:$\varphi(n)=\sum_{i=1}^{n}[gcd(n,i)==1] $ 即与\(n\)互质的个数 特别的 \(1\)算入在内
值得一提的是 欧拉函数是一个积性函数
有一数论函数$f(x) $ 满足\(f(ab)=f(a)f(b)\)且\((a,b)=1\)时 我们称其为积性函数
数论函数:可以粗浅的理解为定义域在正整数上的函数
特别的 若数论函数满足对于任意\(a,b\)均满足\(f(ab)=f(a)f(b)\) 那么我们称其为完全积性函数
一般而言 欧拉函数的计算方式为:\(\varphi(n)=n\prod (1-\frac{1}{p_i})\) 其中\(p_i\)为\(n\)的质因子
它是一个欧拉函数 也就意味着它可以在线性时间被求解 具体而言:(\(p\)为质数)
若\(p|n\)且\(p^2|n\) 此时 \(\varphi(n \times p)=\varphi(n)\times p\) 用上面那个式子拆开即可得到
若\(p|n\)但\(p^2\nmid n\) 那么\(\varphi(n \times p)=\varphi(n)\times \varphi(p)\) 由积性函数性质可知
当然 它还有一个很重要的性质:
\(\varphi(n)*1=Id(n)\)
定义:\(*\)为卷积符号(此处专值狄利克雷卷积)
卷积定义:
设有两个数论函数\(f(a),g(a)\) 那么他们的卷积就是:\(\sum_{d|n} f(d)g(\frac{n}{d})\)\(Id(i)\)为基本函数 其取值为\(Id(i)=i\)
(好SB)
这个性质会在杜教筛里面用到
莫比乌斯函数:
$\mu $为莫比乌斯函数
对于一个数\(d\),有\(d=p_1^{c_1}p_2^{c_2}..\)
其取值为:
换句话说就是:为1则函数值为1,,每个质因子只出现一次且质因子个数为奇数则为-1,为偶数则为 1;出现了不止一次则为0
当然 这玩意也是积性函数 同样可以线性求
其有一个极其重要的性质:
\(\sum_{d|n}\mu (d)=[n=1]\)
考虑证明:
\(\sum_{d|n}\mu (d)=\sum_{i=0}^{k}(-1)^iC_{k}^{i}\) 直接二项式定理解决
莫比乌斯反演
其有一基本形式:
将\(F\)回套入\(f\)中即可
有一拓展形式:
若\(f(i)=\sum_{d=1}^{\lfloor \frac{n}{i} \rfloor }g(d\times i)\)
那么\(g(i)=\sum_{d=1}^{\lfloor \frac{n}{i} \rfloor }f(d\times i)\mu(d)\)
考虑证明:
定义:卷积单位元为:\(\varepsilon(i)=[i==1]\)
有:$\mu*1= \varepsilon $ 证明显然
莫比乌斯函数还有一常见转换:
\([gcd(i,j)==1]->\)\(\varepsilon(gcd(i,j))=\sum_{d|gcd(i,j)}\mu(d)\)
剩下的要做题 还没做呢 做到了再说
杜教筛:
可以在\(O(n^{\frac{2}{3}})\)时间内求解某积性函数的前缀和
条件较为苛刻:
假如\(f(n)\)为积性函数 若能找到另一个积性函数\(g(n)\) 使得\(g(n)\)和\((f*g)(n)\)都可以快速求出 那么就可以求出\(f(n )\)
具体求解:
定义\(S(n)=\sum_{i=1}^{n}f(i)\) 有基本事实:\((f*g)(i)=\sum{d|i}f(d)g(\frac{n}{d})=\sum_{x=1}\sum_{xy=i}f(x)g(y)\)
发现可以提项数 那么改写为:
移项即得:
\(g(1)S(n)=\sum_{i=1}^{n}(f*g)(i)-\sum_{x=2}^{n}g(x)S(\lfloor \frac{n}{x}\rfloor)\)
后半部分整除分块+递归即可 前半部分直接求解 因为杜教筛的前提就是\((f*g)(n)\)可快速求出
例如欧拉函数 由于\(\varphi*1=Id\) 所以直接定义\(g=1\) 公式直接减缩为:
\(S(n)=\frac{n(n+1)}{2}-\sum_{y=2}^{n}S(\lfloor \frac{n}{y} \rfloor )\)
莫比乌斯函数类似 由于$\mu *1=\varepsilon $ 仍定义\(g=1\) 化简得:
\(S(n)=1-\sum_{y=2}^{n}S(\lfloor \frac{n}{y} \rfloor )\)
附上模板:(当\(O(n^{\frac{2}{3}})\)时 欧拉筛所刷长度也需要为\(O(n^{\frac{2}{3}})\))
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxn=5e6+10;
const int INF=0x3f3f3f3f;
int t,n,mu[maxn],vis[maxn],prime[maxn],tot,limit=5e6,sum[maxn],cnt;
unordered_map<int,int>mp;
void init(){
int mx=5e6;tot=0;
mu[1]=1;
for(int i=2;i<=mx;i++){
if(!vis[i])prime[++tot]=i,mu[i]=-1;
vis[i]=1;
for(int j=1;j<=tot&&i*prime[j]<=mx;j++){
vis[i*prime[j]]=1;
if(i%prime[j]==0){
mu[i*prime[j]]=0;break;
}
else mu[i*prime[j]]=mu[i]*mu[prime[j]];
}
}
for(int j=1;j<=mx;j++)mu[j]+=mu[j-1];
}
void init2(){
int mx=5e6;cnt=0;
limit=mx;
sum[1]=1;memset(vis,0,sizeof(vis));
for(int i=2;i<=mx;i++){
if(!vis[i])prime[++cnt]=i,sum[i]=i-1;
vis[i]=1;
for(int j=1;j<=tot&&i*prime[j]<=mx;j++){
vis[i*prime[j]]=1;
if(i%prime[j]==0){
sum[i*prime[j]]=sum[i]*prime[j];
break;
}
else sum[i*prime[j]]=sum[i]*(prime[j]-1);
}
}
for(int i=2;i<=mx;i++)sum[i]+=sum[i-1];
}
int dfs(int x){
if(x<=limit)return mu[x];
if(mp.find(x)!=mp.end())return mp[x];
int ans=1;
for(int l=2,r;l<=x;l=r+1){
r=x/(x/l);
ans=ans-(r-l+1)*dfs(x/l);
}
mp[x]=ans;return ans;
}
int dfs1(int x){
if(x<=limit)return sum[x];
if(mp.find(x)!=mp.end())return mp[x];
int ans=x*(x+1)/2;
for(int l=2,r;l<=x;l=r+1){
r=x/(x/l);
ans=ans-(r-l+1)*dfs1(x/l);
}
mp[x]=ans;return ans;
}
signed main(){
std::ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>t;
init(),init2();
while(t--){
cin>>n;
cout<<dfs1(n)<<" ";
mp.clear();
cout<<dfs(n)<<"\n";
mp.clear();
}
return 0;
}
当然 杜教筛的常数是巨大的
在有些题目 你会发现它只要你求一个前缀和就够了 没有必要求出所有的前缀和
那么这时候 我们就可以选择换一种做法:Min_25筛
Min_25筛
在说明Min_25筛的具体做法之前 先说明其与杜教筛相比的好处与坏处:
好处:时间小 条件不那么严苛
坏处:只能求一个值 代码长度比杜教筛长
它解决如下问题:
求\(\sum_{i=1}^{n}f(i)\) ,满足\(f(x)\)为积性函数 且\(f(p)\)在质数处容易计算/低阶多项式
那么如何求解?:(以luogu模板 Min_25筛为例)
Part 1 基础分类:
我们将数域分成质数和合数 即:
\(\sum_{1\leq q\leq n}f(p)+\sum_{1\leq p^e\leq n,1\leq p\leq \sqrt n}f(p^e)(\sum_{1\leq i\leq n/p^e 且 minp>p}f(i))\)
其中\(minp\)为\(i\)的最小质因子大小
这样,整个式子就变成了两个部分,第一部分是所有质数的\(f\)之和,另一部分是枚举最小质因子后,求所有最小质因子大于这个质因子的\(f\)之和。
Part 2 初始处理
我们不妨设一个\(g(n,i)\)表示为在1到\(n\)中做\(i\)次筛法所剩下的数的贡献 (好吧人话就是要么是质数要么合数的最小质因子大于\(p_i\))
由于一个合数一定有一个质因子小于\(\sqrt n\)
那么我们就可以列出方程:
其中\(-g(p_{i-1},i-1)\)是因为在前面计算的时候把质数已经算进去了 而\(g(n/p_j^k,i-1)\)会使得其重复计算 于是减去
但是!这里有一个要注意的点 我们的\(p_j^k\)其实就是\(f'\) 即贡献(\(f'\)是什么先不用管
相当于我们可以把\(g\)写成若干个\(f'\)的性质 而他们相乘结果不变 这表明 \(f'\)必须要是一个完全积性函数
回到例题 我们发现所给\(f\)并不是完全积性函数 所以操作不合法 那如何去做?
我们发现 虽然\(p(p-1)\)不是积性函数 但是\(p^2\)和\(p\)却是完全积性函数!所以我们拆开算就可以了
当然 你可以发现\(g(p_{i-1},i-1)\)是可以预处理的 因为它就等于质数个数
但是还有一个我们需要考虑的问题 :\(n\)的范围 由于\(n\)很大 所以不能直接转移
但是你又发现我们转移的时候都是通过\(n/p_j^k\)转移的 而在\(n\)范围内有效值就只有\(2\sqrt n\)个 可以用\(id\)预处理 具体而言 对于其小于\(\sqrt n\)的开一个数组 大于\(\sqrt n\)的也开一个数组 但是其下标为\(n/k\) 其中\(k\)为值
显然\(g\)可以循环枚举 于是压维计算
Part 3 计算答案:
类似的 我们定义\(S(n,i)\) 与\(g\)的定义一样 显然答案为\(S(n,0)\)
考虑转移:先定义\(sp_i\)为小于等于\(i\)的所有质数个数
当\(e\)为1时 \(f(p_k^e)\)为质数贡献 而前面已经算过 所以不能计算
直接递归计算即可 好像不用记忆化 我也不知道为什么
一些小细节:
计算时不用把1计算入内 在最后使得答案加1即可
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxn=2e5+10;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
const int inv6=166666668;
int n,sqt;
int prime[maxn],vis[maxn],tot,sum1[maxn],sum2[maxn];//1次 2次
int w[maxn],id1[maxn],id2[maxn],cnt;
int g1[maxn],g2[maxn];
void init(int x){
for(int i=2;i<=x;i++){
if(!vis[i]){
prime[++tot]=i;
sum1[tot]=(sum1[tot-1]+i)%mod;
sum2[tot]=(sum2[tot-1]+i*i)%mod;
}
vis[i]=1;
for(int j=1;j<=tot&&i*prime[j]<=x;j++){
vis[i*prime[j]]=1;
if(i%prime[j]==0)break;
}
}
}
int S(int x,int y){
if(prime[y]>=x)return 0;
int k=x<=sqt?id1[x]:id2[n/x];
int ans=(((g2[k]-g1[k])-(sum2[y]-sum1[y]))%mod+mod)%mod;
for(int i=y+1;i<=tot&&prime[i]*prime[i]<=x;i++){
int pe=prime[i];
for(int e=1;pe<=x;e++,pe*=prime[i]){
int pos=pe%mod;
ans=(ans+pos*(pos-1)%mod*(S(x/pe,i)+(e!=1))%mod)%mod;
if(ans<0)ans+=mod;
}
}
return ans%mod;
}
signed main(){
std::ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>n;
sqt=sqrt(n);
init(sqt);
for(int l=1;l<=n;){
int r=n/(n/l);
w[++cnt]=n/l;
g1[cnt]=w[cnt]%mod;
g2[cnt]=g1[cnt]*(g1[cnt]+1)%mod*(2*g1[cnt]+1)%mod*inv6%mod-1;
g1[cnt]=g1[cnt]*(g1[cnt]+1)/2%mod-1;
if(n/l<=sqt)id1[n/l]=cnt;
else id2[n/(n/l)]=cnt;
l=r+1;
}
for(int i=1;i<=tot;i++){
for(int j=1;j<=cnt&&prime[i]*prime[i]<=w[j];j++){
int k=(w[j]/prime[i]<=sqt?id1[w[j]/prime[i]]:id2[n/(w[j]/prime[i])]);\
g1[j]=(g1[j]-prime[i]*(g1[k]-sum1[i-1]+mod)%mod)%mod;
g2[j]=(g2[j]-prime[i]*prime[i]%mod*(g2[k]-sum2[i-1])%mod)%mod;
if(g1[j]<0)g1[j]+=mod;
if(g2[j]<0)g2[j]+=mod;
}
}
cout<<(S(n,0)+1)%mod<<"\n";
return 0;
}
后记:
用了大概半个上午左右 反正数论吗,多总结总没有错
有挺多东西是挺模糊的 写了一遍清晰了很多 当然肯定还有问题
最近就学了这么多 以后学到了新的再加
浙公网安备 33010602011771号