UPD 2019/01/05 最近学到了杜教筛,正好把以前的代码优化了一遍^_^
首先约定两个记号: 表示如果命题 为真则值为 ,否则为 ; 表示 与 互质。
先从一个问题开始:求
其中
代表莫比乌斯函数。
解:由公式
得
变换一下枚举顺序,得
备注:(2)中的 与 分别代表(1)中的 与 。
称集合 为对 的关键点,则有一个重要定理:对 的关键点包含于对 的关键点
所以分块+递推即可,最多只会关系到 个值,由积分知识得复杂度
那么可不可以继续优化呢?答案是可以的。
首先用线性筛预处理较小的值,而对较大的值进行递推,可以优化效率。
设预处理 个值,则由积分知识的复杂度 ,容易发现当 时最优,复杂度
具体实现时还有一个问题:怎么存储对 的关键点?
用哈希表或map当然可以,在这里给大家介绍一个简单方法,可以做到空间复杂度
- 当 时,可以暴力开数组存下来;
- 当 时,注意到 两两不同,所以可以以 为索引用另一个数组存下来。
类似的,我们可以推出杜教筛的一般形式:设
令
分别是他们的前缀和,则
如果 都可以快速求出,那么 也可以在 内求出
如果令
那么这就是上题的解法。
例题:51nod 1244 莫比乌斯函数之和
求
解:答案就是
话不多说,直接上代码。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=8000005;
ll n,n2,mu[N],tot,a,b,ar[N],vis[N];
int p[N],v[N];
void init(){
mu[1]=1;
for(ll i=2;i<N;i++){
if(!v[i])p[++p[0]]=i,mu[i]=-1;
for(ll j=1;j<=p[0]&&i*p[j]<N;j++){
v[i*p[j]]=1;
if(!(i%p[j]))break;
mu[i*p[j]]=-mu[i];
}
}
for(ll i=2;i<N;i++)mu[i]+=mu[i-1];
}
ll _solve(ll a){
if(a<N)return mu[a];
if(vis[n/a])return ar[n/a];vis[n/a]=1;
ll ans=1;
for(ll i=2,j;i<=a;i=j+1)j=a/(a/i),ans-=(j-i+1)*_solve(a/i);
return ar[n/a]=ans;
}
ll solve(ll a){n=a,n2=sqrt(n+0.5),memset(vis,0,sizeof(vis));return _solve(n);}
int main(){
init(),scanf("%lld%lld",&a,&b),printf("%lld",solve(b)-solve(a-1));
return 0;
}
BZOJ3944 Sum
直接上结论:
第一问取
得
第二问取
得
#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=2500000;
ll t,n,phi0[N],mu0[N],q[N],vis0[N],mu[N],phi[N],vis[N];
void init(){
scanf("%lld",&t);
mu0[1]=phi0[1]=1;
for(ll i=2;i<N;i++){
if(!vis0[i])q[++q[0]]=i,mu0[i]=-1,phi0[i]=i-1;
for(ll j=1;j<=q[0]&&i*q[j]<N;j++){
ll k=i*q[j];
vis0[k]=1;
if(!(i%q[j])){phi0[k]=phi0[i]*q[j];break;}
mu0[k]=-mu0[i],phi0[k]=phi0[i]*(q[j]-1);
}
}
for(ll i=2;i<N;i++)mu0[i]+=mu0[i-1],phi0[i]+=phi0[i-1];
}
pair<ll,ll> get(ll a){
if(a<N)return make_pair(mu0[a],phi0[a]);
if(vis[n/a])return make_pair(mu[n/a],phi[n/a]);vis[n/a]=1;
ll ans=1,ans2;
if(a&1)ans2=a*((a+1)>>1);
else ans2=(a>>1)*(a+1);
for(ll i=2,j;i<=a;i=j+1){
j=a/(a/i);pair<ll,ll> p=get(a/i);
ans -=(j-i+1)*p.first;
ans2-=(j-i+1)*p.second;
}
mu[n/a]=ans,phi[n/a]=ans2;
return make_pair(ans,ans2);
}
void solve(){
while(t--){
scanf("%lld",&n);
memset(vis,0,sizeof(vis));
pair<ll,ll>s=get(n);
printf("%lld %lld\n",s.second,s.first);
}
}
int main(){
init(),solve();
return 0;
}
例题:51nod 1237 最大公约数之和 V3
(原题题面有误)求
解:由莫比乌斯反演,得原式=
后面一部分可以通过分块搞定,问题在于怎么快速求出欧拉函数的前缀和
容易想到用杜教筛,在上面的方法中令
那么即可轻松搞定。
记得取模!记得取模!记得取模!重要的事情说三遍!(注意 本身已经超出了 的范围)
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=15000000,p=1000000007,p2=500000004;
ll n,n2,phi0[N],ans,phi[N],vis[N];
int pr[N],v[N];
void init(){
scanf("%lld",&n),n2=sqrt(n+0.5);
phi0[1]=1;
for(ll i=2;i<N;i++){
if(!v[i])pr[++pr[0]]=i,phi0[i]=i-1;
for(ll j=1;j<=pr[0]&&i*pr[j]<N;j++){
v[i*pr[j]]=1;
if(!(i%pr[j])){phi0[i*pr[j]]=phi0[i]*pr[j];break;}
phi0[i*pr[j]]=phi0[i]*(pr[j]-1);
}
}
for(ll i=2;i<N;i++)(phi0[i]+=phi0[i-1])%=p;
}
ll get(ll a){
if(a<N)return phi0[a];
if(vis[n/a])return phi[n/a];vis[n/a]=1;
ll ans=((a%p)*p2%p)*((a+1)%p)%p;
for(ll i=2,j;i<=a;i=j+1){
j=a/(a/i);
ll cont=((j-i+1)%p)*((p-get(a/i))%p)%p;
(ans+=cont)%=p;
}
return phi[n/a]=ans;
}
void solve(){
for(ll i=1,j;i<=n;i=j+1){
j=n/(n/i);
ll cont=(((n/i)%p)*((n/i)%p)%p)*((get(j)-get(i-1)+p)%p)%p;
(ans+=cont)%=p;
}
printf("%lld",ans);
}
int main(){
init(),solve();
return 0;
}
例题:51nod 1238 最小公倍数之和 V3
(原题题面有误)求
解:原式=
只看后面,得
所以原式=
我们需要快速求出
在上面的方法中令
分块+杜教筛即可轻松搞定。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=15000000,p=1000000007,p2=500000004,p4=250000002,p6=166666668;
ll n,n2,phi0[N],ans,phi[N],vis[N];
int pr[N],v[N];
void init(){
scanf("%lld",&n),n2=sqrt(n+0.5);
phi0[1]=1;
for(ll i=2;i<N;i++){
if(!v[i])pr[++pr[0]]=i,phi0[i]=i-1;
for(ll j=1;j<=pr[0]&&i*pr[j]<N;j++){
v[i*pr[j]]=1;
if(!(i%pr[j])){phi0[i*pr[j]]=phi0[i]*pr[j];break;}
phi0[i*pr[j]]=phi0[i]*(pr[j]-1);
}
}
for(ll i=2;i<N;i++)(phi0[i]*=i*i%p)%=p;
for(ll i=2;i<N;i++)(phi0[i]+=phi0[i-1])%=p;
}
ll sum1(ll a){return ((a%p)*((a+1)%p)%p)*p2%p;}
ll sum2(ll a){return ((a%p)*((a+1)%p)%p)*((((a<<1)+1)%p)*p6%p)%p;}
ll sum3(ll a){ll x=sum1(a);return x*x%p;}
ll get(ll a){
if(a<N)return phi0[a];
if(vis[n/a])return phi[n/a];vis[n/a]=1;
ll ans=sum3(a);
for(ll i=2,j;i<=a;i=j+1){
j=a/(a/i);
ll cont=((sum2(j)-sum2(i-1))%p)*((p-get(a/i))%p)%p;
(ans+=cont)%=p;
}
return phi[n/a]=ans;
}
void solve(){
for(ll i=1,j;i<=n;i=j+1){
j=n/(n/i);
ll cont=((p+sum1(j)-sum1(i-1))%p)*(get(n/i)%p)%p;
(ans+=cont)%=p;
}
printf("%lld",ans);
}
int main(){
init(),solve();
return 0;
}