洛谷 1829 [国家集训队]Crash的数字表格 / JZPTAB 题解

博客围绕求∑(i=1到n)∑(j=1到m)lcm(i,j)(n,m<=1e7)的问题展开。先给出示例数据,输入4 5输出122。接着采用反演思路推导式子,通过一系列变换和整理,利用等差数列求和、整除分块等方法求解,分析时间复杂度小于O(n)。

题意简述

∑i=1n∑j=1mlcm(i,j)\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{m}lcm(i,j)i=1nj=1mlcm(i,j)

(其中n,m&lt;=1e7n,m&lt;=1e7n,m<=1e7)

数据

输入
4 5
输出
122

思路

一看就知道是反演。。。
来推式子吧!
g=gcd(i,j)g=gcd(i,j)g=gcd(i,j),n&lt;=mn&lt;=mn<=m
原式
=∑i=1n∑j=1mijg这个就是化了一下lcm(i,j)=∑d=1n∑i=1n∑j=1m[g==d]ijd这个就是提前枚举gcd(i,j),然后后面找哪些会算到=∑d=1n∑id=dn∑jd=dm[g==1]id∗jdd=∑d=1n∑i=1n/d∑j=1m/d[g==1]ijdi,j换成枚举d的几倍,注意换成枚举倍数后i变成id,j变成jd,所以多乘一个d2,原来是ijd,变成ijd=∑d=1n∑i=1n/d∑j=1m/d∑q∣gμ(q)ijd把[g==1]换成了ϵ(g),然后用了一下反演式=∑d=1n∑q=1n/d∑i=1n/d∑j=1m/d[q∣g]μ(q)ijd提前枚举q,看后面有哪些算到的=∑d=1n∑q=1n/d∑iq=1n/d∑jq=1m/d[1∣g]μ(q)iq∗jq∗di,j有变成了枚举q的倍数,注意i变成iq,j变成jq,所以多乘一个q2=∑d=1n∑q=1n/d∑i=1n/dq∑j=1m/dqμ(q)ijq2d稍作整理,去掉了没什么用的[1∣g](因为不管g多少这个都成立)=∑d=1nd∑q=1n/dq2μ(q)∑i=1n/dqi∑j=1m/dqj进一步的整理,把无关的提到最前 =\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{m}\frac{ij}{g}\\ 这个就是化了一下lcm(i,j)\\ =\sum\limits_{d=1}^{n}\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{m}[g==d]\frac{ij}{d}\\ 这个就是提前枚举gcd(i,j),然后后面找哪些会算到\\ =\sum\limits_{d=1}^{n}\sum\limits_{id=d}^{n}\sum\limits_{jd=d}^{m}[g==1]\frac{id*jd}{d}\\ =\sum\limits_{d=1}^{n}\sum\limits_{i=1}^{n/d}\sum\limits_{j=1}^{m/d}[g==1]ijd\\ i,j换成枚举d的几倍,注意换成枚举倍数后i变成id,j变成jd,所以多乘一个d^2,原来是\frac{ij}{d},变成ijd\\ =\sum\limits_{d=1}^{n}\sum\limits_{i=1}^{n/d}\sum\limits_{j=1}^{m/d}\sum\limits_{q|g}\mu(q)ijd\\ 把[g==1]换成了\epsilon(g),然后用了一下反演式\\ =\sum\limits_{d=1}^{n}\sum\limits_{q=1}^{n/d}\sum\limits_{i=1}^{n/d}\sum\limits_{j=1}^{m/d}[q|g]\mu(q)ijd\\ 提前枚举q,看后面有哪些算到的\\ =\sum\limits_{d=1}^{n}\sum\limits_{q=1}^{n/d}\sum\limits_{iq=1}^{n/d}\sum\limits_{jq=1}^{m/d}[1|g]\mu(q)iq*jq*d\\ i,j有变成了枚举q的倍数,注意i变成iq,j变成jq,所以多乘一个q^2\\ =\sum\limits_{d=1}^{n}\sum\limits_{q=1}^{n/d}\sum\limits_{i=1}^{n/dq}\sum\limits_{j=1}^{m/dq}\mu(q)ijq^2d\\ 稍作整理,去掉了没什么用的[1|g](因为不管g多少这个都成立)\\ =\sum\limits_{d=1}^{n}d\sum\limits_{q=1}^{n/d}q^2\mu(q)\sum\limits_{i=1}^{n/dq}i\sum\limits_{j=1}^{m/dq}j\\ 进一步的整理,把无关的提到最前\\ =i=1nj=1mgijlcm(i,j)=d=1ni=1nj=1m[g==d]dijgcd(i,j),=d=1nid=dnjd=dm[g==1]didjd=d=1ni=1n/dj=1m/d[g==1]ijdi,jdiidjjdd2dijijd=d=1ni=1n/dj=1m/dqgμ(q)ijd[g==1]ϵ(g)=d=1nq=1n/di=1n/dj=1m/d[qg]μ(q)ijdq,=d=1nq=1n/diq=1n/djq=1m/d[1g]μ(q)iqjqdi,jqiiqjjqq2=d=1nq=1n/di=1n/dqj=1m/dqμ(q)ijq2d[1g]g=d=1ndq=1n/dq2μ(q)i=1n/dqij=1m/dqj
然后现在我们会发现,什么都好算了:

  1. 最后面那两个sigmasigmasigma:就是等差数列求和,设sum(n,m)=(∑i=1ni)(∑j=1mj)sum(n,m)=(\sum\limits_{i=1}^{n}i)(\sum\limits_{j=1}^{m}j)sum(n,m)=(i=1ni)(j=1mj),显然根据搞死求和公式,它=(n(n+1)2)(m(m+1)2)=(\frac{n(n+1)}{2})(\frac{m(m+1)}{2})=(2n(n+1))(2m(m+1))。后面两个sigmasigmasigma就变成了sum(n/dq,m/dq)sum(n/dq,m/dq)sum(n/dq,m/dq),就珂以O(1)O(1)O(1)求了。
  2. 如果能求出μ(q)\mu(q)μ(q)的所有值,那么q2μ(q)q^2\mu(q)q2μ(q)的所有值也能求,同时也就能求q2μ(q)q^2\mu(q)q2μ(q)的前缀和。那么∑q=1n/dq2μ(q)∑i=1n/dqi∑j=1m/dqj=∑q=1n/dq2μ(q)s(n/dq,m/dq)\sum\limits_{q=1}^{n/d}q^2\mu(q)\sum\limits_{i=1}^{n/dq}i\sum\limits_{j=1}^{m/dq}j=\sum\limits_{q=1}^{n/d}q^2\mu(q)s(n/dq,m/dq)q=1n/dq2μ(q)i=1n/dqij=1m/dqj=q=1n/dq2μ(q)s(n/dq,m/dq)就珂以整除分块了,设其为f(n/d,m/d)f(n/d,m/d)f(n/d,m/d)
  3. 设了不少函数!现在原式变为∑d=1ndf(n/d,m/d)\sum\limits_{d=1}^{n}df(n/d,m/d)d=1ndf(n/d,m/d),这个整除分块太tm^{tm}tm明显了吧!

计算一下时间复杂度。一次f(n/d,m/d)f(n/d,m/d)f(n/d,m/d)最坏是f(n,m)f(n,m)f(n,m),也就是O(n)O(\sqrt{n})O(n)
外层在套一层整除分块,也就是O(n)O(\sqrt{n})O(n)
两层一乘,O(n)O(n)O(n)过了。
(而实际上并不是准确的O(n)O(n)O(n),稍微要小一点,应该是O(∑i=1nn/i)O(\sum\limits_{i=1}^{\sqrt{n}}\sqrt{n/i})O(i=1nn/i),反正小于O(n)O(n)O(n)就是了。O(n)O(n)O(n)是能过的,我们的代码当然珂以过)
代码:

#include<bits/stdc++.h>
using namespace std;
#define int long long

#define mod 20101009
#define N 10000010
namespace Flandle_Scarlet
{
    int mu[N],primes[N];
    bool notp[N];
    int d2mu[N];
    //d2mu[i]=i*i*μ(i)的前缀和
    

    void Init()
    {
        mu[1]=notp[1]=1;
        int n=10000000;
        int &cnt=primes[0];
        for(int i=2;i<=n;++i)//线性筛mu
        {
            if (!notp[i])
            {
                primes[++cnt]=i;
                mu[i]=-1;
            }
            for(int j=1;j<=cnt and i*primes[j]<=n;++j)
            {
                int u=primes[j];
                notp[i*u]=1;
                if (i%u==0)
                {
                    mu[i*u]=0;
                    break;
                }
                else
                {
                    mu[i*u]=-mu[i];
                }
            }
        }

        for(int i=1;i<=n;++i)
        {
            d2mu[i]=(d2mu[i-1]+i*i%mod*(mu[i]+mod))%mod;
            //这个式子十分显然,但是注意取膜时的优先级
        }
    }

    int sum(int n,int m)
    //也就是上面说的sum
    //但是一定要注意优先级!!!我错了好多次!!!
    {
        return ((n*(n+1)/2)%mod*(m*(m+1)/2)%mod)%mod;
    }
    int f(int n,int m)
    //也就是上面说的f
    {
        int ans=0;
        for(int l=1,r;l<=min(n,m);l=r+1)
        {
            r=min(n/(n/l),m/(m/l));
            //d2mu[r]-d2mu[l-1]计算中间和
            //sum(n/l,m/l)是相同部分
            ans=(ans+(d2mu[r]-d2mu[l-1]+mod)*sum(n/l,m/l)%mod)%mod;
        }
        return ans;
    }
    int calc(int n,int m)
    //主求解函数
    {
        int ans=0;
        for(int l=1,r;l<=min(n,m);l=r+1)
        {
            r=min(n/(n/l),m/(m/l));
            //这个太显然了。。。不想解释
            ans=(ans+(r-l+1)*(l+r)/2%mod*f(n/l,m/l)%mod)%mod;
        }
        return ans;
    }

    int n,m;
    void Main()
    {
        Init();
        scanf("%lld%lld",&n,&m);
        printf("%lld\n",calc(n,m));
    }
};
main()
{
    Flandle_Scarlet::Main();
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值