[Luogu P1829] [BZOJ 2154] [国家集训队]Crash的数字表格

洛谷传送门

BZOJ传送门

题目背景

提示:原 P1829 半数集问题 已经迁移至 P1028 数的计算

题目描述

今天的数学课上,Crash小朋友学习了最小公倍数(Least Common Multiple)。对于两个正整数 a a b b LCM(a,b) L C M ( a , b ) 表示能同时整除 a a b b 的最小正整数。例如, LCM(6,8)=24 L C M ( 6 , 8 ) = 24

回到家后,Crash还在想着课上学的东西,为了研究最小公倍数,他画了一张 NM N ∗ M 的表格。每个格子里写了一个数字,其中第 i i 行第j j 列的那个格子里写着数为 LCM(i,j) L C M ( i , j ) 。一个 45 4 ∗ 5 的表格如下:

1    2    3    4    5
2    2    6    4    10
3    6    3    12    15
4    4    12    4    20

看着这个表格,Crash想到了很多可以思考的问题。不过他最想解决的问题却是一个十分简单的问题:这个表格中所有数的和是多少。当 N N M M 很大时,Crash就束手无策了,因此他找到了聪明的你用程序帮他解决这个问题。由于最终结果可能会很大,Crash只想知道表格里所有数的和 mod 20101009 m o d   20101009 的值。

输入输出格式

输入格式:

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

输出格式:

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

输入输出样例

输入样例#1:

4 5

输出样例#1:

122

说明

30%的数据满足 N,M103 N , M ≤ 10 3

70%的数据满足 N,M105 N , M ≤ 10 5

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

解题分析

首先推一波式子,这里我们不妨设 n<=m n <= m

i=1nj=1mlcm(i,j)=i=1nj=1mijgcd(i,j)=d=1ni=1nj=1mijd[gcd(i,j)=d]=d=1ndi=1ndj=1mdij[gcd(i,j)=1] ∑ i = 1 n ∑ j = 1 m l c m ( i , j ) = ∑ i = 1 n ∑ j = 1 m i j g c d ( i , j ) = ∑ d = 1 n ∑ i = 1 n ∑ j = 1 m i j d [ g c d ( i , j ) = d ] = ∑ d = 1 n d ∑ i = 1 ⌊ n d ⌋ ∑ j = 1 ⌊ m d ⌋ i j [ g c d ( i , j ) = 1 ]

现在我们单独拿出右边的一堆进行处理。设 F(x,y)=xi=1yj=1ij[gcd(i,j)=1](x<=y) F ( x , y ) = ∑ i = 1 x ∑ j = 1 y i j [ g c d ( i , j ) = 1 ] ( x <= y ) ,那么
F(x,y)=i=1xj=1yijk|gcd(i,j)μ(k)=k=1xμ(k)i=1xj=1yij[k|gcd(i,j)] =k=1xμ(k)×k2i=1xkj=1yk[1|gcd(i,j)]=k=1xμ(k)×k2×(1+xk)×xk2×(1+yk)×yk2 F ( x , y ) = ∑ i = 1 x ∑ j = 1 y i j ∑ k | g c d ( i , j ) μ ( k ) = ∑ k = 1 x μ ( k ) ∑ i = 1 x ∑ j = 1 y i j [ k | g c d ( i , j ) ]   = ∑ k = 1 x μ ( k ) × k 2 ∑ i = 1 ⌊ x k ⌋ ∑ j = 1 ⌊ y k ⌋ [ 1 | g c d ( i , j ) ] = ∑ k = 1 x μ ( k ) × k 2 × ( 1 + ⌊ x k ⌋ ) × ⌊ x k ⌋ 2 × ( 1 + ⌊ y k ⌋ ) × ⌊ y k ⌋ 2

将这个式子代回原式,得到:
i=1nj=1mlcm(i,j)=d=1ndk=1ndμ(k)×k2×(1+nkd)×nkd2×(1+mkd)×mkd2 ∑ i = 1 n ∑ j = 1 m l c m ( i , j ) = ∑ d = 1 n d ∑ k = 1 ⌊ n d ⌋ μ ( k ) × k 2 × ( 1 + ⌊ n k d ⌋ ) × ⌊ n k d ⌋ 2 × ( 1 + ⌊ m k d ⌋ ) × ⌊ m k d ⌋ 2

看起来太凌乱, 而且已经没法化简了, 于是我们套路地更改枚举项,设 T=kd T = k d
d=1ndk=1ndμ(k)×k2×(1+nkd)×nkd2×(1+mkd)×mkd2=T=1n(1+nT)×nT2×(1+mT)×mT2Tk|Tk×μ(k) ∑ d = 1 n d ∑ k = 1 ⌊ n d ⌋ μ ( k ) × k 2 × ( 1 + ⌊ n k d ⌋ ) × ⌊ n k d ⌋ 2 × ( 1 + ⌊ m k d ⌋ ) × ⌊ m k d ⌋ 2 = ∑ T = 1 n ( 1 + ⌊ n T ⌋ ) × ⌊ n T ⌋ 2 × ( 1 + ⌊ m T ⌋ ) × ⌊ m T ⌋ 2 T ∑ k | T k × μ ( k )

我们要做的最后一件事就是快速求出 k|Tk×μ(k) ∑ k | T k × μ ( k ) 了(因为前面可以下底分块快速求)。

考虑 f(x)=k|xk×μ(k) f ( x ) = ∑ k | x k × μ ( k )

  • x x 为质数时, 显然f(x)=1×1+x×(1)=1x f ( x ) = 1 × 1 + x × ( − 1 ) = 1 − x
  • x=pri×m x = p r i × m 时且 prim p r i ∤ m 时, 除了原来的因数, 我们还引入了 pri×a1,pri×a2...(a1,a2...|m) p r i × a 1 , p r i × a 2 . . . ( a 1 , a 2 . . . | m ) 这些因数, 而 μ(pri×a1)=μ(a1) μ ( p r i × a 1 ) = − μ ( a 1 ) , 所以 f(x)=pri×f(m)+f(m) f ( x ) = − p r i × f ( m ) + f ( m )
  • x=pri×m x = p r i × m 时且 pri|m p r i | m 时, 没有新的约数, 而 μ(pri2)=μ(pri2×a1)...=0 μ ( p r i 2 ) = μ ( p r i 2 × a 1 ) . . . = 0 , 所以 f(pri×m)=f(m) f ( p r i × m ) = f ( m )

最后这个式子与 μ μ 一点关系都没有, 筛都不需要筛出来…

我们对 x×f(x) x × f ( x ) 做一个前缀和, 套上下底分块就可以 AC A C 了。

总复杂度 O(N+N) O ( N + N )

代码如下:

#include 
#include 
#include 
#include 
#include 
#include 
#define R register
#define IN inline
#define gc getchar()
#define W while
#define ll long long
#define MOD 20101009ll
#define MX 10000500
int pri[MX], pcnt, n, m;
ll F[MX], sum[MX];
bool npr[MX];
void get()
{
    F[1] = sum[1] = 1;
    R int i, j, tar;
    for (i = 2; i <= n; ++i)
    {
        if(!npr[i]) pri[++pcnt] = i, F[i] = 1 - i;
        for (j = 1; j <= pcnt; ++j)
        {
            tar = pri[j] * i;
            if(tar > n) break;
            npr[tar] = true;
            if(!(i % pri[j])) {F[tar] = F[i]; break;}
            F[tar] = F[i] * (1 - pri[j]);
        }
        sum[i] = (sum[i - 1] + F[i] * i % MOD + MOD) % MOD;
    }
}
int main(void)
{
    scanf("%d%d", &n, &m);
    if(n > m) std::swap(n, m);
    get(); ll ans = 0;
    for (R int lef = 1, rig, sum1, sum2; lef <= n; lef = rig + 1)
    {
        rig = std::min(n / (n / lef), m / (m / lef));
        sum1 = 1ll * (n / lef) * (n / lef + 1) / 2 % MOD;
        sum2 = 1ll * (m / lef) * (m / lef + 1) / 2 % MOD;
        ans  += 1ll * sum1 * sum2 % MOD * (sum[rig] - sum[lef - 1] + MOD) % MOD;
        ans %= MOD;
    }
    printf("%lld", (ans % MOD + MOD) % MOD);
}

你可能感兴趣的:(数学,莫比乌斯反演,数学,莫比乌斯反演)