「BZOJ」2154 Crash的数字表格-莫比乌斯反演

本文介绍了一种利用莫比乌斯反演求解特殊数学问题的方法:给定两个正整数N和M,计算N*M表格中各单元格最小公倍数之和模20101009的值。

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

Description

今天的数学课上,Crash小朋友学习了最小公倍数(Least Common Multiple)。对于两个正整数a和b,LCM(a, b)表示能同时被a和b整除的最小正整数。例如,LCM(6, 8) = 24。回到家后,Crash还在想着课上学的东西,为了研究最小公倍数,他画了一张N*M的表格。每个格子里写了一个数字,其中第i行第j列的那个格子里写着数为LCM(i, j)。一个4*5的表格如下: 1 2 3 4 5 2 2 6 4 10 3 6 3 12 15 4 4 12 4 20 看着这个表格,Crash想到了很多可以思考的问题。不过他最想解决的问题却是一个十分简单的问题:这个表格中所有数的和是多少。当N和M很大时,Crash就束手无策了,因此他找到了聪明的你用程序帮他解决这个问题。由于最终结果可能会很大,Crash只想知道表格里所有数的和mod 20101009的值。

Input

输入的第一行包含两个正整数,分别表示N和M。

Output

输出一个正整数,表示表格中所有数的和mod 20101009的值。

Sample Input

4 5

Sample Output

122

【数据规模和约定】

100%的数据满足N, M ≤ 10^7。

Solution

题目中如此之大的 nm 已经将解法赤裸裸地暴露了出来……
最近学习了莫(meng)比(bi)乌(yi)斯(dun)反(luan)演(gao),于是还是要写一篇题解麻痹自己
回到这道题,这道题要求出一个很奇葩简单的值——
ans=ni=1mj=1lcm(i,j)%mod
其中 mod= 反正是一个很大的数就对了
乍看之下——毫无头绪,莫比乌斯反演似乎不太喜欢 lcm 的样子……
我们有什么快速求出 lcm 的式子吗?(先尝试 O(n2) 左右的暴力)
有的—— lcm(a,b)=abgcd(a,b)
诶,这样一变,就出现了莫比乌斯反演很喜欢的 gcd
好,我们写下来
ans=ni=1mj=1ijgcd(i,j)
同时按照惯例我们要设 n<m
这样就是一个 O(n2logn) 的暴力了
当然, n107 是要干嘛的?跑一年都跑不完吧?!!
我们必须要优化。
考虑到很多数的 gcd 很有可能相同,我们可以考虑把它们一起做
我们枚举他们的 gcd —— d

ans=nd=1ni=1mj=1[gcd(i,j)=d]ijd

这样似乎还把时间变长了,不急,我们不必枚举ij,只需要枚举他们是d的多少倍就行了,不是么?以下的ij代表倍数

ans=nd=1ndi=1mdj=1[gcd(i,j)=1]ijd

诶,我们又看到了一个很好玩的式子gcd(i,j)=1,我们知道,一个数a=1等价于d|aμ(d)=1,所以我们再换一换(注意 d 被提前了)

ans=nd=1dndi=1mdj=1ijk|gcd(i,j)μ(k)

我们知道,一个数k|gcd(i,j) 相当于k|ik|j,我们再次转化

ans=nd=1dndi=1mdj=1ijmin(i,j)k=1[k|i][k|j]μ(k)

同样的,我们也可以枚举 k 来代替枚举 ij,然后枚举倍数即可

ans=nd=1dndk=1μ(k)nkdi=1mkdj=1ijk2

接下来,神奇的事情发生了,这样的ij只有 [nkd][mkd] 组,并且
i×j=(1+2++[nkd])(1+2++[mkd])=[nkd][nkd+1][mkd][mkd+1]4
这是可以 O(1) 计算的,那么答案就是

ans=41nd=1dndk=1μ(k)k2[ndk][mdk]([ndk]+1)([mdk]+1)

好了,现在降低成 O(nlogn)n+n2+n3++nnnlogn)了,然而依然无法接受

但是我们可以发现,这个式子可以使用分块优化

我们记 f(n,m)=nk=1μ(k)k2[nk][mk]([nk]+1)([mk]+1)
我们再记 g(n,m)=41nd=1df(nd,md)
很容易就会发现,我们只需要求出 ans=g(n,m),通过分块枚举 ndmd,可以使得外层循环(g)降低为 O(n),同理,内层循环(f)也可以降低为 O(n),这样总时间复杂度就是 O(n)
Code:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 10000000
#define M 5000000
#define mod 20101009
#define inv 15075757
int mu[N+5],h[N+5],p[M+5],cnt;
int sum[N+5],num[N+5],n,m;
int ans2(int a,int b)
{
    if(a>b)swap(a,b);
    int last;
    int re=0;
    for(int i=1;i<=a;i=last+1)
    {
        last=min(a/(a/i),b/(b/i));
        re=(re+1ll*(sum[last]-sum[i-1])*(a/i)%mod*(b/i)%mod*(a/i+1)%mod*(b/i+1)%mod)%mod;
    }
    return re;
}
int ans(int a,int b)
{
    if(a>b)swap(a,b);
    int last;
    int re=0;
    for(int i=1;i<=a;i=last+1)
    {
        last=min(a/(a/i),b/(b/i));
        re=(re+1ll*(num[last]-num[i-1])*ans2(a/i,b/i)%mod)%mod;
    }
    return (1ll*inv*re%mod+mod)%mod;
}
int main()
{
    mu[1]=1;
    scanf("%d%d",&n,&m);
    int mx=max(n,m);
    for(int i=2;i<=mx;i++)
    {
        if(!h[i])
        {
            mu[i]=-1;
            p[++cnt]=i;
        }
        for(int j=1;j<=cnt;j++)
        {
            if(p[j]*i>N)break;
            h[p[j]*i]=1;
            if(i%p[j]==0)break;
            mu[p[j]*i]=-mu[i];
        }
    }
    for(int i=1;i<=mx;i++)
    {
        sum[i]=(1ll*i*i*mu[i]%mod+sum[i-1])%mod;
        num[i]=(i+num[i-1])%mod;
    }
    printf("%d\n",ans(n,m));
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值