杜教筛
前置知识:莫比乌斯反演
杜教筛
杜教筛可以在非线性时间内求积性函数前缀和。
我们假设要求前缀和的积性函数为,设前缀和。
我们找一个合适的函数,我们看看两个函数的狄利克雷卷积的前缀和。
先枚举再枚举:
把除以
提出
也就是说
再看的值:
只要我们找到使得可以算出的前缀和、的前缀和,我们靠这个式子也就可以递归。
递归前,我们可以预处理内的前缀和,再用unordered_map
记忆化,就能达到的复杂度。
注:unordered_map
为C++11
的STL
本题,当为时,找的为,则
当为时,找的为,则
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int MAXN=5000005;
bool isp[MAXN+1];
int phi[MAXN+1],mu[MAXN+1],pcnt,n,prime[MAXN+1];
ll ans,smu[MAXN+1],sphi[MAXN+1];
unordered_map<int,ll>Sphi,Smu;
ll Get_smu(unsigned int n) {//mu
if(n<=MAXN) return smu[n];
if(Smu[n]) return Smu[n];
ull ans=1ll;
for(unsigned int l=2,r; l<=n; l=r+1) {//整除分块
r=n/(n/l);
ans-=(r-l+1ll)*Get_smu(n/l);//递归
}
return Smu[n]=ans;
} //mu*I=e
ll Get_sphi(unsigned int n) {//phi
if(n<=MAXN)return sphi[n];
if(Sphi[n]) return Sphi[n];
ull ans=n*(n+1ll)>>1;
for(unsigned int l=2,r; l<=n; l=r+1) {
r=n/(n/l);
ans-=(r-l+1ll)*Get_sphi(n/l);
}
return Sphi[n]=ans;
}//phi*I=id
int main() {
sphi[1]=smu[1]=mu[1]=phi[1]=1;
for(int i=2; i<=MAXN; ++i) {//筛出一些mu,phi并计算前缀和
if(!isp[i]) {
prime[++pcnt]=i;
mu[i]=-1;
phi[i]=i-1;
}
for(int j=1,x; j<=pcnt&&(x=i*prime[j])<=MAXN; ++j) {
isp[x]=1;
if(i%prime[j]) {
mu[x]=-mu[i];
phi[x]=phi[i]*(prime[j]-1);
} else {
mu[x]=0;
phi[x]=phi[i]*prime[j];
break;
}
}
sphi[i]=sphi[i-1]+phi[i];
smu[i]=smu[i-1]+mu[i];//预处理一些前缀和
}
int t,n;
scanf("%d",&t);
while(t--) {
scanf("%d",&n);
printf("%lld %lld\n",Get_sphi(n),Get_smu(n));
}
return 0;
}
一道题
先用枚举
把除以
把三个都提出来
再把代入
前面的题也做过,等价于
把提前
把提出
后面的都除以
把两个都提出
把乘进去,设
先枚举,再枚举,就是
提出
后面的就是
由莫比乌斯反演得
设,若能求出它的前缀和,就可以整出分块了。
考虑用杜教筛。
设,则
后面的部分就是,是等于的
即,前缀和为。
而且的前缀和为,然后就可以杜教筛了。
注:代码中,我手写了哈希表。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN=5000005;
namespace Hash {//图版哈希表
const int mo=233333;
struct hash_table {
int head[mo],htcnt;
struct hashtable {
ll id,val;
int nxt;
} ht[mo];
void add(int u,ll v,ll w) {
++htcnt;
ht[htcnt].id=v;
ht[htcnt].val=w;
ht[htcnt].nxt=head[u];
head[u]=htcnt;
}
void clr() {
memset(head,0,sizeof(head));
memset(&ht,0,sizeof(&ht));
htcnt=0;
}
void insert(ll x,ll i) {
int k=x%mo;
add(k,x,i);
}
int query(ll x) {
for(int i=head[x%mo]; i; i=ht[i].nxt)
if(ht[i].id==x)
return ht[i].val;
return -1;
}
};
} using namespace Hash;
int prime[MAXN+1],pcnt,phi[MAXN+1],mod;
int sphi[MAXN+1],inv6;//inv6为6的逆元
hash_table Sphi;
ll n;
int Getsphi(ll m) {//查询
if(m<=MAXN) return sphi[m];
return Sphi.query(m);
}
int S(int n) {//1~n和
return (n*(n+1ll)>>1)%mod;
}
int Sp(int n) {//g的前缀和
return n*(n+1ll)%mod*(n<<1|1ll)%mod*inv6%mod;
}
void Get(ll m,ll d) {//杜教筛
if(m<=MAXN||~Sphi.query(m))return ;//查询过就不管
ll ans=S(m%mod);
ans=ans*ans%mod;//f*g前缀和
for(ll l=2,r; l<=m; l=r+1) {
r=m/(m/l);
Get(m/l,d*l);//先递归
ans=(ans+mod-(Sp(r%mod)+0ll+mod-Sp((l-1)%mod))%mod*Getsphi(m/l)%mod)%mod;
}
Sphi.insert(m,ans);//记忆化
}
int Get_sphi(ll m) {
Get(m,1);
return Getsphi(m);//查询
}
int fastpow(int a,int k=mod-2) {//快速幂求逆元
int base=1;
while(k) {
if(k&1)base=base*1ll*a%mod;
a=a*1ll*a%mod;
k>>=1;
}
return base;
}
int main() {
scanf("%d%lld",&mod,&n);
inv6=fastpow(6);
sphi[1]=phi[1]=1;
for(int i=2; i<=MAXN; ++i) {//线性筛前缀和
if(!phi[i]) {
prime[++pcnt]=i;
phi[i]=i-1;
}
for(int j=1,x; j<=pcnt&&(x=i*prime[j])<=MAXN; ++j) {
if(i%prime[j]) {
phi[x]=phi[i]*(prime[j]-1);
} else {
phi[x]=phi[i]*prime[j];
break;
}
}
sphi[i]=(sphi[i-1]+phi[i]*1ll*i%mod*i)%mod;
}
ll tmp1,ans=0;
for(ll l=1,r; l<=n; l=r+1) {//整除分块求答案
r=n/(n/l);
tmp1=S((n/l)%mod);
tmp1=tmp1*tmp1%mod;
ans=(ans+tmp1*(Get_sphi(r)+0ll+mod-Get_sphi(l-1))%mod)%mod;
}
printf("%lld\n",ans);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】