杜教筛(进阶篇)
一道更比一道毒瘤
[51 nod 1227] 平均最小公倍数#
其实就是求
因此我们只需要求出函数就行了
设
而我们知道
我们也知道
其中表示狄利克雷卷积
因此变成
带进整个式子
考虑把拆出来,那么只有等于1是会有贡献,一共有n个1,所以总贡献是n
问题转化为求的前缀和,我们考虑卷上一个,化简可以得到,上杜教筛就行了
复杂度
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6+7;
typedef long long LL;
const LL mod = 1e9+7;
LL phi[N];
LL f[N];
int v[N],prime[N],tot=0;
LL Pow(LL a,LL b)
{
LL res=1;
while(b)
{
if(b&1) res=1ll*res*a%mod;
a=1ll*a*a%mod;
b>>=1;
}
return res;
}
LL inv6=Pow(6,mod-2);
LL inv2=Pow(2,mod-2);
LL sqr(LL n)
{
n%=mod;
return n*(n+1)%mod*(2*n+1)%mod*inv6%mod;
}
LL sum(LL n)
{
return 1ll*(1+n)*n%mod*inv2%mod;
}
void init(int n)
{
phi[1]=1;
for(int i=2;i<=n;i++)
{
if(!v[i])
{
v[i]=i;
prime[++tot]=i;
phi[i]=i-1;
}
for(int j=1;j<=tot;j++)
{
if(prime[j]>v[i]||i*prime[j]>n) break;
v[i*prime[j]]=prime[j];
if(i%prime[j]==0)
{
phi[i*prime[j]]=phi[i]*prime[j];
break;
}
else phi[i*prime[j]]=phi[i]*(prime[j]-1);
}
}
for(int i=1;i<=n;i++)
phi[i]=(phi[i-1]+phi[i]*i%mod)%mod;
}
unordered_map<LL,LL> s,vis;
LL Sum(LL n)
{
if(n<=1e6) return phi[n];
if(vis[n]) return s[n];
LL res=sqr(n);
LL l=2,r;
for(;l<=n;l=r+1)
{
r=(n/(n/l));
res=(res-(sum(r)-sum(l-1)+mod)%mod*Sum(n/l)%mod+mod)%mod;
}
vis[n]=1;
s[n]=res;
return res;
}
LL calc(LL n)
{
LL res=n;
LL l=1,r;
for(;l<=n;l=r+1)
{
r=n/(n/l);
res=(res+1ll*(r-l+1)%mod*Sum(n/l)%mod)%mod;
}
return res*inv2%mod;
}
int main()
{
freopen("minave.in","r",stdin);
freopen("minave.out","w",stdout);
init(1e6);
LL a,b;
cin>>a>>b;
cout<<(calc(b)-calc(a-1)+mod)%mod;
return 0;
}
SP20173 DIVCNT2 - Counting Divisors (square)#
考虑先分析的性质
显然这是一个积性函数,我们考虑从每一个质因子分别考虑
我们继续观察,??
也就是除以他们的质因子的次幂是0或1
那么有没有什么是和指数相关的的呢?
没错,就是,但是有负数,怎么办呢
没错,加个平方就行了
事实上和我们分析的一样,
当然和是一样的
莫反一下
这个东西可以用整除分块做一下,现在的问题变为得到
和的前缀和
根据一些容斥的知识,我们可以得到
这个可以用整除分块做到
而有一个众所众知的式子
证明的话就是考虑计算每个数在n个数中有多少倍数
这个也可以整除分块做到
不过两个套起来就是了
我们考虑先预处理和的前项的前缀和,询问的时候直接调用
复杂度和杜教筛类似是
当然,这个题有毒
它的是,预处理要处理到
因此不仅空间大,常数也大
这份代码在SPOJ上并不能跑过
#include<bits/stdc++.h>
using namespace std;
const int N = 5e7+5;
const int M = 1e7+7;
typedef long long LL;
LL d[N];
int mu[N];
int t[N];
bool v[N];
int prime[M],tot=0;
void init(int n)
{
mu[1]=1;
d[1]=1;
for(int i=2;i<=n;i++)
{
if(!v[i])
{
v[i]=1;
prime[++tot]=i;
d[i]=2;
mu[i]=-1;
t[i]=2;
}
for(int j=1;j<=tot;j++)
{
if(i*prime[j]>n) break;
v[i*prime[j]]=1;
if(i%prime[j]==0)
{
t[i*prime[j]]=t[i]+1;
d[i*prime[j]]=d[i]/t[i]*(t[i]+1);
mu[i*prime[j]]=0;
break;
}
t[i*prime[j]]=2;
d[i*prime[j]]=d[i]*d[prime[j]];
mu[i*prime[j]]=mu[i]*mu[prime[j]];
}
}
for(int i=1;i<=n;i++)
{
d[i]+=d[i-1];
t[i]=t[i-1]+abs(mu[i]);
}
}
LL R;
LL SumU(LL n)
{
if(n<=R) return t[n];
LL res=0;
for(LL i=1;i*i<=n;i++)
res=(res+mu[i]*(n/i/i));
return res;
}
LL SumD(LL n)
{
if(n<=R) return d[n];
LL res=0;
LL l=1,r;
for(;l<=n;l=r+1)
{
r=n/(n/l);
res=res+(r-l+1)*(n/l);
}
return res;
}
void solve(LL n)
{
LL res=0;
LL l=1,r;
LL last=0;
LL m=sqrt(n);
for(LL i=1;i<=m;i++)
if(mu[i]!=0) res=res+SumD(n/i);
l=m+1;last=SumU(m);
for(;l<=n;l=r+1)
{
r=n/(n/l);
LL now=SumU(r);
res=(res+(now-last)*SumD(n/l));
last=now;
}
printf("%lld\n",res);
}
LL a[20000];
LL INF = 1e12;
int main()
{
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
int T;
cin>>T;
LL r;
for(int i=1;i<=T;i++)
{
scanf("%lld",&a[i]);
r=max(r,a[i]);
}
if(r<=10000) R=10000;
else R=N-10;
init(R);
for(int i=1;i<=T;i++)
solve(a[i]);
return 0;
}
[51nod1222]最小公倍数计数#
还是先做成前缀相减的形式
问题转化为求
发现最大不会超过
因此
可以考虑枚举,计算后面的值
观察后三项的上指标
会发现,如果,后面的式子就为0
类似的,的上指标都可以被后面的约束掉
因此,后边部分等于
我们考虑强制让 然后乘一个系数就行了
考虑不同的的状态
1:,即三个相等,枚举到的合法的d就加一就行了
2:,即后两个相等,算出合法的的数量乘以3
3: 与2类似
4:
发现只需要枚举到n的三次根就行了,i只需要枚举到n的平方根就行了
总复杂度与杜教筛类似为,它的系数是6
可以再计算后面的时候一块计算
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL N =1e6+7;
LL mu[N],v[N];
LL prime[N],tot=0;
void init(LL n)
{
mu[1]=1;
for(LL i=2;i<=n;i++)
{
if(!v[i])
{
v[i]=i;
mu[i]=-1;
prime[++tot]=i;
}
for(LL j=1;j<=tot;j++)
{
if(prime[j]>v[i]||i*prime[j]>n) break;
v[i*prime[j]]=prime[j];
if(i%prime[j]==0)
{
mu[i*prime[j]]=0;
break;
}
mu[i*prime[j]]=mu[i]*mu[prime[j]];
}
}
}
LL S(LL n)
{
LL res=0;
for(LL k=1;k*k<=n;k++)
{
if(!mu[k]) continue;
LL cnt=0;
LL limit=n/(k*k);
for(LL i=1;i*i*i<=limit;i++)
{
for(LL j=i+1;i*j*j<=limit;j++)
cnt+=(LL)(limit/(i*j)-j)*6+3;//i,j,k+i,j,j
cnt+=(LL)(limit/(i*i)-i)*3;//i,i,j
cnt++;//i,i,i
}
res=res+mu[k]*cnt;
}
return (res+n)/2;
}
int main()
{
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
init(1e6);
LL l,r;
cin>>l>>r;
cout<<S(r)-S(l-1);
return 0;
}
[51nod1220] 约数之和#
求
其中表示的因子之和
首先有引理
证明与SDOI约数个数和类似,不赘述了
直接莫反
设,
考虑和的关系
会发现,其实就是把中每个数的权值乘上他出现的次数
因此两个式子是等价的,问题进一步转化为
我们只需要求出的前缀和就可以整除分块了
接着会发现
因此
众所众知,是积性函数,可以线性筛求出来
类似于杜教筛,预处理除的前缀和,剩下的整除分块暴力做,可以做到
别忘了最后还剩一个,卷上一个,就可以杜教筛了
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6+7;
typedef long long LL;
int mu[N];
LL f[N];
int v[N],prime[N],tot=0;
const int mod = 1e9+7;
LL d[N],t[N],g[N];
void init(LL n)
{
mu[1]=1;
d[1]=1;
for(int i=2;i<=n;i++)
{
if(!v[i])
{
v[i]=i;
prime[++tot]=i;
mu[i]=-1;
d[i]=i+1;
g[i]=i;
t[i]=i+1;
}
for(int j=1;j<=tot;j++)
{
if(prime[j]>v[i]||i*prime[j]>n) break;
v[i*prime[j]]=prime[j];
if(i%prime[j]==0)
{
g[i*prime[j]]=g[i]*prime[j];
t[i*prime[j]]=t[i]+g[i*prime[j]];
d[i*prime[j]]=d[i]/t[i]*t[i*prime[j]];
mu[i*prime[j]]=0;
break;
}
else
{
mu[i*prime[j]]=mu[i]*mu[prime[j]];
d[i*prime[j]]=d[i]*d[prime[j]];
g[i*prime[j]]=prime[j];
t[i*prime[j]]=1+prime[j];
}
}
}
for(int i=1;i<=n;i++)
{
f[i]=(f[i-1]+mu[i]*i%mod+mod)%mod;
d[i]=(d[i]+d[i-1])%mod;
}
}
unordered_map<LL,LL> vis,s;
LL Sum(LL n)
{
if(n<=1e6) return f[n];
if(vis[n]) return s[n];
LL res=1;
LL l=2,r;
for(;l<=n;l=r+1)
{
r=n/(n/l);
res=(res-1ll*(l+r)*(r-l+1)/2%mod*Sum(n/l)%mod+mod)%mod;
}
vis[n]=1;
s[n]=res;
return res;
}
LL D(LL n)
{
if(n<=1e6) return d[n];
LL l=1,r;
LL res=0;
for(;l<=n;l=r+1)
{
r=n/(n/l);
res=(res+(l+r)*(r-l+1)/2%mod*(n/l)%mod)%mod;
}
return res;
}
int main()
{
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
init(1e6);
LL n;
cin>>n;
LL res=0;
LL l=1,r;
for(;l<=n;l=r+1)
{
r=n/(n/l);
LL val=D(n/l);
res=(res+(Sum(r)-Sum(l-1)+mod)%mod*val%mod*val%mod)%mod;
}
cout<<res;
return 0;
}
[51nod1584]加权约数和#
求
我们知道这个的答案矩阵是对称的,因此答案即为
先看前半部分
和上一题类似
带进式子
设
我们可以预处理除的前缀和,这样就能快速计算
但是询问的次数非常多,需要我们回答
因此继续化简
这个式子可以通过调和级数的算出来,再计算前缀和就可以查询了
接着看后半部分
问题是处理的前缀和
这个也是可以用线性筛筛出来的,毕竟这是一个积性函数
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL N = 1e6+7;
const LL mod =1e9+7;
LL d[N],t[N],p[N];
LL sd[N],st[N],sp[N];
LL s[N],g[N];
LL mu[N];
LL v[N],prime[N],tot=0;
LL Pow(LL a,LL b)
{
LL res=1;
while(b)
{
if(b&1) res=1ll*res*a%mod;
a=1ll*a*a%mod;
b>>=1;
}
return res;
}
LL inv(LL n)
{
return Pow(n,mod-2);
}
LL f[N];
void init(LL n)
{
mu[1]=1;
sd[1]=1;
d[1]=1;
for(LL i=2;i<=n;i++)
{
if(!v[i])
{
v[i]=i;
prime[++tot]=i;
mu[i]=-1;
d[i]=i+1;
t[i]=i+1;
p[i]=i;
sd[i]=(1+i+(LL)i*i%mod)%mod;
st[i]=(1+i+(LL)i*i%mod)%mod;
sp[i]=(LL)i*i%mod;
}
for(LL j=1;j<=tot;j++)
{
if(v[i]<prime[j]||i*prime[j]>n) break;
LL k=i*prime[j];
v[i*prime[j]]=prime[j];
if(i%prime[j]==0)
{
mu[k]=0;
p[k]=p[i]*prime[j]%mod;
t[k]=(t[i]+p[k])%mod;
d[k]=d[i]*inv(t[i])%mod*t[k]%mod;
sp[k]=sp[i]*prime[j]%mod*prime[j]%mod;
st[k]=(st[i]+sp[i]*prime[j]%mod+sp[k])%mod;
sd[k]=(sd[i]*inv(st[i])%mod*st[k])%mod;
break;
}
else
{
mu[k]=mu[i]*mu[prime[j]];
p[i*prime[j]]=prime[j];
t[i*prime[j]]=1+prime[j];
d[i*prime[j]]=d[i]*d[prime[j]]%mod;
sp[i*prime[j]]=prime[j]*prime[j]%mod;
st[i*prime[j]]=(1+prime[j]+prime[j]*prime[j]%mod)%mod;
sd[i*prime[j]]=sd[i]*sd[prime[j]]%mod;
}
}
}
for(LL i=1;i<=n;i++)
s[i]=(d[i]+s[i-1])%mod;
for(LL i=1;i<=n;i++)
sd[i]=(sd[i-1]+i*sd[i]%mod)%mod;
for(LL i=1;i<=n;i++)
g[i]=1ll*i*d[i]%mod*s[i]%mod;
for(LL d=1;d<=n;d++)
for(LL T=d;T<=n;T+=d)
f[T]=(f[T]+mu[d]*d%mod*d%mod*g[T/d]%mod+mod)%mod;
for(LL i=1;i<=n;i++)
f[i]=(f[i-1]+f[i])%mod;
}
LL calc(LL n)
{
return (2ll*f[n]%mod-sd[n]+mod)%mod;
}
int main()
{
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
init(1e6);
LL T;
cin>>T;
int ca=0;
while(T--)
{
LL n;
ca++;
scanf("%lld",&n);
printf("Case #%d: %lld\n",ca,calc(n));
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通