洛谷3768 简单的数学题 莫比乌斯反演+杜教筛

本文详细解析了一道关于杜教筛算法的数学问题,通过枚举和数学转换,将原始问题转化为可快速求解的形式,并提供了完整的代码实现。

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

Description


由于出题人懒得写背景了,题目还是简单一点好。
输入一个整数n和一个整数p,你需要求出ni=1nj=1i×j×gcd(i,j)(modp)∑i=1n∑j=1ni×j×gcd(i,j)(modp)

对于20%的数据, n1000n≤1000
对于30%的数据, n5000n≤5000
对于60%的数据, n106n≤106 ,时限1s。
对于另外20%的数据, n109n≤109 ,时限3s。
对于最后20%的数据, n1010n≤1010 ,时限6s。
对于100%的数据,5×108p1.1×1095×108≤p≤1.1×109 且p为质数。

Solution


感觉对杜教筛的理解又深一点点

ans=i=1nj=1ni×j×gcd(i,j)=d=1nd3i=1ndj=1ndi×j×[gcd(i,j)==1]ans=∑i=1n∑j=1ni×j×gcd(i,j)=∑d=1nd3∑i=1⌊nd⌋∑j=1⌊nd⌋i×j×[gcd(i,j)==1]

第一波操作照例是枚举gcd,然后再套μ得到
ans=d=1nd3x=1ndx2μ(x)S2(ndx)ans=∑d=1nd3∑x=1⌊nd⌋x2μ(x)S2(⌊ndx⌋)

其中S(n)=ni=1iS(n)=∑i=1ni
T=dxT=dx枚举TT得到
ans=T=1nS2(nT)T2d|Tμ(d)Td

有一个结论是d|Tμ(d)Td=φ(T)∑d|Tμ(d)Td=φ(T),知道了这个就可以继续往下推惹
ans=T=1nS2(nT)T2φ(T)ans=∑T=1nS2(⌊nT⌋)T2φ(T)

这里已经可以分块求,但是还需解决后面的前缀和问题,考虑杜教筛
f(i)=i2φ(i)f(i)=i2φ(i)g(i)=i2g(i)=i2h(n)=ni=1f(i)h(n)=∑i=1nf(i)
容易得到
i=1nd|ig(d)×f(id)=i=1nd|id2×(id)2×φ(id)=i=1ni2d|iφ(d)=i=1ni3∑i=1n∑d|ig(d)×f(id)=∑i=1n∑d|id2×(id)2×φ(id)=∑i=1ni2∑d|iφ(d)=∑i=1ni3
同时
i=1nd|ig(d)×f(id)=i=1nd|id2×(id)2×φ(id)=d=1nd2i=1ndi2×φ(i)=d=1nd2×h(nd)∑i=1n∑d|ig(d)×f(id)=∑i=1n∑d|id2×(id)2×φ(id)=∑d=1nd2∑i=1⌊nd⌋i2×φ(i)=∑d=1nd2×h(⌊nd⌋)

i=1i=1时拉出来得到
h(n)=i=1ni3d=2nd2h(nd)h(n)=∑i=1ni3−∑d=2nd2h(⌊nd⌋)

那么就可以杜教筛直接上了

需要指出的是,ni=1i3=(ni=1i)2∑i=1ni3=(∑i=1ni)2,我怕不是小学奥数题都不会了
这题的模数不一定是质数因此不能用费马小定理求逆元,考虑到n不是特别大可以直接除

Code


#include <stdio.h>
#include <map>

typedef long long LL;
const int N=10000000;

std:: map <LL,LL> map;

int prime[N+5],MOD;
bool not_prime[N+5];
LL f[N+5];

void pre_work() {
    f[1]=1;
    for (int i=2;i<=N;i++) {
        if (!not_prime[i]) {
            prime[++prime[0]]=i;
            f[i]=i-1;
        }
        for (int j=1;i*prime[j]<=N&&j<=prime[0];j++) {
            not_prime[i*prime[j]]=1;
            if (i%prime[j]==0) {
                f[i*prime[j]]=f[i]*prime[j];
                break;
            }
            f[i*prime[j]]=f[i]*f[prime[j]];
        }
    }
    for (int i=1;i<=N;i++) f[i]=(f[i-1]+f[i]*i%MOD*i%MOD)%MOD;
}

LL S(LL n) {
    n=n%MOD;
    return (LL)n*(n+1)/2%MOD;
}

LL S2(LL n)
{
    n=n%MOD;
    if ((LL)n*(n+1)%3==0) return (LL)n*(n+1)/6%MOD*(n*2+1)%MOD;
    else return (LL)(n*2+1)/3*((LL)n*(n+1)/2%MOD)%MOD;
}

LL get_sum(LL n) {
    if (n<=N) return f[n];
    if (map[n]) return map[n];
    LL ret=S(n); ret=ret*ret%MOD;
    for (LL i=2,j;i<=n;i=j+1) {
        j=n/(n/i);
        ret=(ret-(S2(j)-S2(i-1)+MOD)%MOD*get_sum(n/i)%MOD+MOD)%MOD;
    }
    return map[n]=ret;
}

void solve(LL n) {
    LL ans=0;
    for (LL i=1,j;i<=n;i=j+1) {
        j=n/(n/i);
        LL sum1=S(n/i);
        ans=(ans+(sum1*sum1)%MOD*(get_sum(j)-get_sum(i-1)+MOD)%MOD)%MOD;
    }
    printf("%lld\n", (ans+MOD)%MOD);
}

int main(void) {
    LL n; scanf("%d%lld",&MOD,&n);
    pre_work();
    solve(n);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值