[BZOJ3512]DZY Loves Math IV 杜教筛+记忆化搜索

本文探讨了在数论中如何高效地计算特定形式的求和问题,通过将整数分解为质因数的乘积并利用积性函数的性质,提出了一种优化算法。该算法能够有效减少计算复杂度,特别适用于求解形如S(n,m)的表达式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

发现nn只有=105,我们先考虑S(n,m)=mi=1φ(ni)S(n,m)=∑i=1mφ(ni)怎么求。
先把nn分成两部分,n1=pi,即所有质因子一次幂的乘积,n2=pci1in2=∏pici−1,即剩下的。不难发现φ(n)=φ(n1)n2φ(n)=φ(n1)∗n2

S(n,m)=n2i=1mφ(n1i)=n2i=1mφ(n1)φ(i)gcd(n1,i)φ(gcd(n1,i))S(n,m)=n2∑i=1mφ(n1i)=n2∑i=1mφ(n1)φ(i)gcd(n1,i)φ(gcd(n1,i))

然后因为φφ是积性函数,所以当gcd(b,a/b)=1gcd(b,a/b)=1时,φ(a)φ(b)=φ(ab)φ(a)φ(b)=φ(ab),所以
=n2i=1mφ(n1gcd(n1,i))φ(i)gcd(n1,i)=n2i=1mφ(n1gcd(n1,i))φ(i)d|gcd(n1,i)φ(d)=n2∑i=1mφ(n1gcd(n1,i))φ(i)gcd(n1,i)=n2∑i=1mφ(n1gcd(n1,i))φ(i)∑d|gcd(n1,i)φ(d)

这里用d|nφ(d)∑d|nφ(d)代替nn是为了去掉gcd的限制,并且和前面的合并。
=n2i=1mφ(i)d|gcd(n1,i)φ(n1d)=n2d|n1φ(n1d)i=1mdφ(di)=n2d|n1φ(n1d)S(d,md)=n2∑i=1mφ(i)∑d|gcd(n1,i)φ(n1d)=n2∑d|n1φ(n1d)∑i=1⌊md⌋φ(di)=n2∑d|n1φ(n1d)S(d,md)

注意到SS中的参数最多只有nm种,可以记忆化,当n=1n=1的时候就是杜教筛啦。
听说复杂度O(nm+m23)O(nm+m23)。可是还是不太明白求了所有的mdi=1φ(i)∑i=1mdφ(i)为什么还是O(m23)O(m23)。。。
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<map>
#include<cmath>
#include<vector>
#define MP make_pair
#define PB push_back
#define fs first
#define sc second
#define ll long long
#define N 100010
#define U 6000000
#define P 1000000
#define ID(x,y) (x*1234567891+y)
#define up(x,y) x=(x+(y))%mod
using namespace std;
const int mod=1000000007;
bool flag[U+5];
int n,m,pri[U+5],minp[U+5],num,sp[U+5],phi[U+5];
struct hash
{
    vector<pair<ll,ll> > a[P];
    void ins(ll x,ll y)
    {
        int d=x%P;
        a[d].PB(MP(x,y));
    }
    ll qry(ll x)
    {
        int d=x%P;
        for(int i=a[d].size()-1;i>=0;i--)
            if(a[d][i].fs==x) return a[d][i].sc;
        return -1;   
    }
}h;
void getpri()
{
    memset(flag,1,sizeof(flag));
    flag[1]=0;phi[1]=1;
    for(int i=2;i<=U;i++)
    {
        if(flag[i]) pri[++num]=i,phi[i]=i-1,minp[i]=i;
        for(int j=1;j<=num&&i*pri[j]<=U;j++)
        {
            int v=i*pri[j];
            flag[v]=0;minp[v]=pri[j];
            if(i%pri[j]==0) {phi[v]=phi[i]*pri[j];break;}
            phi[v]=phi[i]*phi[pri[j]];          
        }
    }
}
void fj(int x,int *t)
{
    while(x>1)
    {
        t[++t[0]]=minp[x];
        while(x%t[t[0]]==0) x/=t[t[0]];
    }
}
ll qphi(int x)
{
    if(x<=U) return sp[x];
    ll re=((ll)x*(x+1)/2)%mod;
    for(int l=2,r;l<=x;l=r+1)
    {
        r=x/(x/l);
        up(re,mod-qphi(x/l)*(r-l+1)%mod);
    }
    return re;
}
ll S(ll a,ll b)
{
    if(!b) return 0;
    if(h.qry(ID(a,b))!=-1) return h.qry(ID(a,b));
    if(a==1)
    {
        h.ins(ID(a,b),qphi(b));
        return h.qry(ID(a,b));
    }
    int t[12],w=1;
    ll re=0;
    t[0]=0;
    fj(a,t);
    for(int i=1;i<=t[0];i++)
        w*=t[i];
    for(int i=floor(sqrt(w));i;i--)
        if(w%i==0) up(re,S(i,b/i)*phi[w/i]),up(re,S(w/i,b/(w/i))*phi[i]);
    re=re*a/w%mod;
    h.ins(ID(a,b),re);
    return re;  
}
int main()
{
    getpri();
    for(int i=1;i<=U;i++)
        sp[i]=(phi[i]+sp[i-1])%mod; 
    scanf("%d%d",&n,&m);
    ll ans=0;
    for(int i=1;i<=n;i++)
        up(ans,S(i,m));
    printf("%lld",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值