bzoj3529/洛谷P3312 数表 莫比乌斯反演+树状数组

题目分析

首先,我们忽略掉 a a 这个条件,设n<m,那么我们要求的就是:

i=1nj=1md|gcd(i,j)d ∑ i = 1 n ∑ j = 1 m ∑ d | g c d ( i , j ) d

由于此题跟gcd有关,所以我们猜测这道题要用莫比乌斯反演,所以不管三七二十一把莫比乌斯反演的常见应用列上来:
g(k)=ni=1mj=1[gcd(i,j)==k] g ( k ) = ∑ i = 1 n ∑ j = 1 m [ g c d ( i , j ) == k ]
g(k)=k|dμ(dk)ndmd g ( k ) = ∑ k | d μ ( d k ) ⌊ n d ⌋ ⌊ m d ⌋
F(x) F ( x ) x x 的约数和,则答案就被转化为了x=1nF(x)g(x)
xx=1F(x)x|dμ(dx)ndmd ∑ x = 1 x F ( x ) ∑ x | d μ ( d x ) ⌊ n d ⌋ ⌊ m d ⌋
那么就是 ndndmdx|dF(x)μ(dx) ∑ d n ⌊ n d ⌋ ⌊ m d ⌋ ∑ x | d F ( x ) μ ( d x )
喔~如果是求这个的花,我们可以首先枚举约数,求出 F(x) F ( x ) ,然后打表 x|dF(x)μ(dx) ∑ x | d F ( x ) μ ( d x ) 的前缀和,然后用一个常用分块优化处理前面的 ndndmd ∑ d n ⌊ n d ⌋ ⌊ m d ⌋ (详见 莫比乌斯反演
可是,只有小于等于 a a F(x)才会有贡献,怎么办?
离线!将 F(x) F ( x ) 从小到大排序,将询问按照 a a 从小到大排序,然后使用一个树状数组,动态往树状数组里加F(x)μ(dx),前缀和就求树状数组前缀和即可。

代码

#include<bits/stdc++.h>
using namespace std;
const int N=100005;
int Q,n,m,lim,tot,now;
struct question{int n,m,a,id;}q[20005];
struct hans{int v,id;}F[N];
int mu[N],is[N],pri[N],tr[N],ans[20005];
void init() {
    for(int i=1;i<=lim;++i)//预处理F
        for(int j=i;j<=lim;j+=i) F[j].v+=i;
    for(int i=1;i<=lim;++i) F[i].id=i;
    mu[1]=1;
    for(int i=2;i<=lim;++i) {//预处理莫比乌斯函数
        if(!is[i]) pri[++tot]=i,mu[i]=-1;
        for(int j=1;j<=tot&&pri[j]*i<=lim;++j) {
            is[i*pri[j]]=1;
            if(i%pri[j]==0) {mu[i*pri[j]]=0;break;}
            else mu[i*pri[j]]=-mu[i];
        }
    }
}
int cmp1(question x,question y) {return x.a<y.a;}
int cmp2(hans x,hans y) {return x.v<y.v;}
#define lowbit(x) (x&(-x))
void add(int x,int num) {while(x<=lim) tr[x]+=num,x+=lowbit(x);}
int query(int x) {
    int re=0;
    while(x) re+=tr[x],x-=lowbit(x);
    return re;
}
void getans(int x) {
    for(int i=1,j;i<=q[x].n;i=j+1) {//常用分块优化
        j=min(q[x].n/(q[x].n/i),q[x].m/(q[x].m/i));
        ans[q[x].id]+=(q[x].n/i)*(q[x].m/i)*(query(j)-query(i-1));
    }
}
int main()
{
    scanf("%d",&Q);
    for(int i=1;i<=Q;++i) {
        scanf("%d%d%d",&q[i].n,&q[i].m,&q[i].a);
        q[i].id=i;if(q[i].n>q[i].m) swap(q[i].n,q[i].m);
        lim=max(lim,q[i].n);
    }
    init();
    sort(q+1,q+1+Q,cmp1),sort(F+1,F+1+lim,cmp2);
    now=1;
    for(int i=1;i<=Q;++i) {
        while(now<=lim&&F[now].v<=q[i].a) {
            for(int j=F[now].id;j<=lim;j+=F[now].id)
                add(j,F[now].v*mu[j/F[now].id]);
            ++now;
        }
        getans(i);
    }
    for(int i=1;i<=Q;++i) printf("%d\n",ans[i]&0x7fffffff);
    return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值