BZOJ 2154: Crash的数字表格(莫比乌斯反演)

本文解析了一道关于求解大规模矩阵中最小公倍数之和的数论问题,通过引入函数进行逐步化简求解,并利用分块优化技术将复杂度降低到线性级别。

题目描述

传送门

题目大意:有一个n*m的表格,第i行第j列的数是lcm(i,j),求表格内所有数的和对20101009取模的结果(n,m<=10^7)。


题解

简单的数论题。老套路,先假设 n<=m 。题目要求

i=1nj=1m[i,j]

画一下柿子

ans=i=1nj=1mij(i,j)

=d=1ni=1nj=1m[(i,j)=d]ijd

=d=1ndi=1ndj=1md[(i,j)=1]ij

F(x,y)=xi=1yj=1[(i,j=1)]ij ,则
ans=d=1ndF(nd,md)

假如我们已经预先求出了 F ,明显这里分块优化一下,维护d的前缀和,求出答案的时间是 O(n) 的。

但发现好像不能预处理 F F是二元函数),所以只能在线去算。

我们将 F 反演一下

F(x,y)=i=1xj=1yijt|i,t|jμ(t)

=t=1xt2μ(t)i=1xtj=1ytij

我们搞一个新的函数 Sum(x,y)=xi=1yj=1ij=x(x+1)2y(y+1)2

于是

F(x,y)=t=1xt2μ(t)Sum(xt,yt)

我们预处理出 i2μ(i) 的前缀和,再搞一个分块就可以求出 F 了,于是总时间就是O(n)O(n)=O(n)

有时候设出函数,分步化简求解比一步算到尽头要更清晰和简单。

对于单组询问,这样做就可以了。如果是多组询问呢?那就需要更秀的操作,这里就不说了,以后有空再去做加强版吧。


代码

#include <bits/stdc++.h>
#define maxn 10000005
#define MOD 20101009
#define temp (i * prime[j])

using namespace std;

int n, m, cnt;
int prime[maxn], miu[maxn], sd[maxn];
bool Vis[maxn];

void Da(){
    miu[1] = 1;
    for(int i = 2; i <= n; i++){
        if(!Vis[i]){
            prime[++cnt] = i;
            miu[i] = -1;    
        }
        for(int j = 1; j <= cnt && temp <= n; j++){
            Vis[temp] = true;
            if(i % prime[j] == 0){
                miu[temp] = 0;
                break;
            }
            else  miu[temp] = -miu[i];
        }
    }
    for(int i = 1; i <= n; i++)  miu[i] = 1LL * miu[i] * i * i % MOD;
    for(int i = 2; i <= n; i++)  miu[i] = (miu[i] + miu[i-1]) % MOD;
    for(int i = 1; i <= n; i++)  sd[i] = i;
    for(int i = 2; i <= n; i++)  sd[i] = (sd[i] + sd[i-1]) % MOD;
}

int Sum(int x, int y){
    int temp1 = (1LL * x * (x + 1) >> 1) % MOD;
    int temp2 = (1LL * y * (y + 1) >> 1) % MOD;
    return 1LL * temp1 * temp2 % MOD;
}

int F(int x, int y){
    if(x > y)  swap(x, y);
    int res = 0;
    int last;

    for(int i = 1; i <= x; i = last+1){
        last = min(x/(x/i), y/(y/i));
        res = (res + 1LL * (miu[last] - miu[i-1] + MOD) % MOD * Sum(x/i, y/i) % MOD) % MOD;
    }
    return res;
}

int Solve(){
    int ans = 0;
    int last;

    for(int i = 1; i <= n; i = last+1){
        last = min(n/(n/i), m/(m/i));
        ans = (ans + 1LL * (sd[last] - sd[i-1] + MOD) % MOD * F(n/i, m/i) % MOD) % MOD;
    }
    return ans;
}   

int main(){

    scanf("%d%d", &n, &m);

    if(n > m)  swap(n, m);

    Da();

    printf("%d\n", Solve());

    return 0;
}//注意乘法可能爆int

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值