bzoj3529(莫比乌斯反演+树状数组)

本文介绍了一种针对特定数表的高效求和算法,该数表由两维度构成,每个单元格的值等于能同时整除其行列坐标的自然数之和。通过数学变换和离线处理策略,实现了在大规模数据集上的快速计算,并考虑到数值范围限制。

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

Description
有一张 n×m 的数表,其第 i 行第 j 列(1 <= i <= n, 1 <= j <= m)的数值为
能同时整除 i 和 j 的所有自然数之和。给定 a , 计算数表中不大于 a 的数之和。
Input
输入包含多组数据。
输入的第一行一个整数Q表示测试点内的数据组数
接下来Q行,每行三个整数n,m,a(|a| < =10^9)描述一组数据。
1 < =N.m < =10^5 , 1 < =Q < =2×10^4
Output
对每组数据,输出一行一个整数,表示答案模2^31的值。
Sample Input
2
4 4 3
10 10 5
Sample Output
20
148
HINT


F[x]F[x]表示xx的因数之和
然后某一格的数字为F[gcd(i,j)]
不考虑aa的限制
ans=i=1nj=1mF[gcd(i,j)]
=min(n,m)d=gcdF[d]min(nd,md)t=1μtntdmtd=∑d=gcdmin(n,m)F[d]∑t=1min(⌊nd⌋,⌊md⌋)μ(t)∗⌊ntd⌋∗⌊mtd⌋
老套路 令T=tdT=td
=min(n,m)T=1nTmTd|TF(d)μ(Td)=∑T=1min(n,m)⌊nT⌋⌊mT⌋∑d|TF(d)∗μ(Td)
=min(n,m)T=1nTmTg(T)=∑T=1min(n,m)⌊nT⌋⌊mT⌋g(T)
求出g(T)g(T)的前缀和就可以做出来了
那么对于没有aa的限制是可以轻松AC的
那如果有a
我们考虑离线做。
把输入按照aa升序
F(i)数组按照F(i)F(i)升序
按照顺序加入树状数组维护即可
intint取模我们可以直接满溢

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 101000;
int prime[maxn] , miu[maxn];
bool flag[maxn];  
int Q , tr[maxn];
int ans[maxn];
int mx;
const int MOD = 1<<31;
struct data
{
    int n , m , a , id;
}q[20100];
pair<int,int> F[maxn]; 
int read()
{
    int sum = 0;char c = getchar();bool flag = true; 
    while( c < '0' || c > '9' ) {if(c == '-') flag = false;c = getchar();}
    while( c >= '0' && c <= '9' ) sum = sum * 10 + c - 48 , c = getchar();
    if(flag)  return sum;
     else return -sum;
}  
void add(int x,int val)
{
    for(int i = x;i <= mx;i += i & (-i)) tr[i] += val;
}
int find(int x)
{
    int sum = 0;
    for(int i = x;i;i -= i&(-i)) sum += tr[i];
    return sum;
}
bool mycmp(data a,data b)
{
    return a.a < b.a;
}
void first()
{
    Q = read(); 
    for(int i = 1;i <= Q;++i)
        q[i].n = read() , q[i].m = read() , q[i].a = read() , q[i].id = i,
        mx = max(mx , max(q[i].n , q[i].m)); 
    sort(q + 1,q + Q + 1,mycmp);
    miu[1] = 1;
    for(int i = 2;i <= mx;++i)
    {
        if(!flag[i])
            prime[++prime[0]] = i , miu[i] = -1;
        for(int j = 1;j <= prime[0] && i * prime[j] <= mx;++j) 
        {
            flag[i * prime[j]] = true;
            if(i % prime[j] == 0) break;
            miu[i * prime[j]] = -miu[i];
        }
    }
    for(int i = 1;i <= mx;++i)
        for(int j = i;j <= mx;j += i)
            F[j].first += i;
    for(int i = 1;i <= mx;++i) F[i].second = i;
    sort(F + 1,F + mx + 1);
    return;
}
void solve(int x)
{
    int n = q[x].n , m = q[x].m , id = q[x].id;
    int T = 1;
    if( n > m ) swap( n , m );
    while(T <= n)
    {
        int i = min( n / ( n / T ) , m / ( m / T ));
        ans[id] += 1ll * ( n / T ) * ( m / T ) * (find(i) - find(T - 1)) ;
        T = i + 1;
    }
    return;
}
int main()
{
    first();
    int now = 1;
    for(int i = 1;i <= Q;++i)
    {
        while(now <= mx && F[now].first <= q[i].a)
        {
            for(int j = F[now].second;j <= mx;j += F[now].second)
                add( j , F[now].first * miu[j / F[now].second]);
            now++;
        }
        solve(i);
    }
    for(int i = 1;i <= Q;++i)
        printf("%d\n",ans[i]&0x7fffffff);
    return 0;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值