[题解] Crash的数字表格 / JZPTAB

题目描述

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

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

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

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

输入格式

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

输出格式

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

样例输入

4 5
C++

样例输出

122
C++

数据范围及提示

30% 的数据满足 n,m103

70% 的数据满足 n,m105

100% 的数据满足 n,m107

解题思路

i=1nj=1mijgcd(i,j)=i=1nj=1md=1min(n,m)ijd[gcd(i,j)=d]=i=1nj=1md=1min(n,m)ijd[gcd(i,j)=d]=d=1min(n,m)d2i=1ndj=1mdijd[gcd(i,j)=1]=d=1min(n,m)di=1ndj=1md[gcd(i,j)=1]ij=d=1min(n,m)di=1ndj=1mdkgcd(i,j)μ(k)ij=d=1min(n,m)di=1ndj=1mdkikjμ(k)ij

这个式子可以被分成两部分解决,我们令
f(n,m)=i=1nj=1mkikjμ(k)ij
那么原式就等于
d=1min(n,m)df(nd,md)
可以通过整除分块计算,而 f(n,m) 的求解则是经典问题
f(n,m)=i=1nj=1mkikjμ(k)ij=k=1min(n,m)μ(k)i=1ni=1mij[ki][kj]=k=1min(n,m)μ(k)k2i=1nkj=1mkij

g(n,m)=i=1nj=1mij=(n+1)×n2×(m+1)×m2
那么式子可以写作
f(n,m)=k=1min(n,m)μ(k)k2g(nk,mk)
可以整除分块计算

代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>

using namespace std;
const int MAXN = 10000000 + 10;
const int MOD = 20101009;

inline int read() {
    int x = 0;
    char ch = getchar();

    while (ch < '0' || ch > '9') {
        ch = getchar();
    }

    while ('0' <= ch && ch <= '9') {
        x = x * 10 + ch - '0';
        ch = getchar();
    }

    return x;
}

int prime[MAXN], mu[MAXN], cnt;
bool isNotPrime[MAXN];

long long sum[MAXN];

inline void init (int n) {
    mu[1] = 1;

    for (register int i=2; i<=n; i++) {
        if (!isNotPrime[i]) {
            prime[++cnt] = i;
            mu[i] = -1;
        }

        for (register int j=1; j<=cnt; j++) {
            int v = i * prime[j];
            if (v > n) break;

            isNotPrime[v] = true;

            if (i % prime[j] == 0) {
                mu[v] = 0;
                break;
            } else {
                mu[v] = -mu[i];
            }
        }
    }

    for (register int i=1; i<=n; i++) {
        sum[i] = (sum[i-1] + ((1LL * i * i) % MOD * (mu[i] + MOD) % MOD)) % MOD;
    }
}

inline long long calc2(int n, int m) {
    return (1LL * n * (n + 1) / 2 % MOD) * (1LL * m * (m + 1) / 2 % MOD) % MOD;
}

inline long long calc(int n, int m) {
    long long ans = 0;
    int r = 0;

    for (register int l=1; l<=min(n, m); l = r + 1) {
        r = min(n / (n / l), m / (m / l));
        ans = (ans + (sum[r] - sum[l-1] + MOD) % MOD * calc2(n / l, m / l) % MOD) % MOD;
    }

    return ans;
}

inline long long solve(int n, int m) {
    long long ans = 0;
    int r = 0;

    for (register int l=1; l<=min(n, m); l = r + 1) {
        r = min(n/(n/l), m/(m/l));
        ans = (ans + ((1LL * (l + r) * (r - l + 1) / 2)) % MOD * calc(n / l, m / l)) % MOD;
    }

    return ans;
}

int main(){
    int n = read();
    int m = read();

    init(max(n, m));

    printf("%lld\n", solve(n, m));
    return 0;
}
C++
评论 在线讨论
      加载更多