[SPOJ] DIVCNT2 - Counting Divisors (square) 积性函数前缀和

题面
题目要我们求

i=1nσ0(i2)

i 分解质因数pα11pα22...pαkk
就是求
i=1nj=1k(2αj+1)

考虑每个乘式选 2αj 还是 1 ,枚举集合就是
i=1nS{1,2,...,k}2|S|jαj

S 集合看成选αj>0的,把 jαj 看成枚举每个 >0 αj ,事实上就是枚举 i 的约数,每个约数的贡献是2ω(d),其中 ω(i) 表示 i 的质因数个数,所以就是
i=1nd|n2ω(d)

其实把 ω(d) 看成每个质因数选或不选,就是 d 的无平方因子约数的个数,所以式子变成
i=1nd|np|dμ2(p)

改成枚举 μ2(i) ,就是
i=1nμ2(i)j=1niσ0(j)

于是利用整除分块,我们只要知道 μ2(i) σ0(i) 的前缀和,按照杜教筛的复杂度分析,就能在预处理前 n23 项的情况下, O(n23) 求出。
前者就是 1 n 的无平方因子数个数,考虑容斥 O(n)
i=1nμ2(i)=i=1nμ(i)ni2

后者就是sb套路,整除分块 O(n)
i=1nσ0(i)=i=1nni

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#define ll long long
using namespace std;
const int maxn=50000000;
ll n;
int pri[10000000],num,mu[maxn+5],d[maxn+5],sm[maxn+5],sd[maxn+5],ci[maxn+5];
bool flag[maxn+5];
void getpri(int n)
{
    memset(flag,1,sizeof(flag));
    flag[1]=0;mu[1]=1;d[1]=1;
    for(int i=2;i<=n;i++)
    {
        if(flag[i]) pri[++num]=i,mu[i]=-1,ci[i]=1,d[i]=2;
        for(int j=1;j<=num&&i*pri[j]<=n;j++)
        {
            int v=i*pri[j];
            flag[v]=0;ci[v]=1;
            if(i%pri[j]==0) {ci[v]+=ci[i];mu[v]=0;d[v]=d[i]*(ci[v]+1)/ci[v];break;}
            mu[v]=-mu[i];d[v]=(d[i]<<1);
        }
    }
}
ll qsd(ll m)
{
    if(m<=maxn) return sd[m];
    ll re=0;
    for(ll l=1,r=1,k;l<=m;l=r+1)
    {
        k=m/l;r=m/k;
        re+=(ll)(r-l+1)*k;
    }
    return re;
}
ll qsm(ll m)
{
    if(m<=maxn) return sm[m];
    ll gen=round(sqrt(m)+1),re=0;
    for(ll i=1;i<=gen;i++)
        re+=mu[i]*(m/(i*i));    
    return re;  
}
ll solve()
{
    ll re=0;
    for(ll l=1,r=1,k;l<=n;l=r+1)
    {
        k=n/l;r=n/k; 
        re+=qsd(k)*(qsm(r)-qsm(l-1));
    }
    return re;
}
int main()
{
    int ca,lim=maxn;
    scanf("%d",&ca);
    if(ca>800) lim=10000;
        getpri(lim);    
        for(int i=1;i<=lim;i++)
            sm[i]=sm[i-1]+mu[i]*mu[i],sd[i]=sd[i-1]+d[i];
    while(ca--)
    {
        scanf("%lld",&n);
        printf("%lld\n",solve());
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值