【BZOJ3529】数表(SDOI2014)-莫比乌斯反演+树状数组

测试地址:数表
做法:本题需要用到莫比乌斯反演+树状数组。
首先忽略 a a 的限制,我们要求的是:
ans=i=1nj=1mD(gcd(i,j))
其中 D(i) D ( i ) i i 的约数和。
我们把上式改成枚举公因数d,不妨设 nm n ≤ m ,则:
ans=nd=1D(d)ni=1mj=1[gcd(i,j)=d] a n s = ∑ d = 1 n D ( d ) ∑ i = 1 n ∑ j = 1 m [ gcd ( i , j ) = d ]
f(d)=ni=1mj=1[gcd(i,j)=d] f ( d ) = ∑ i = 1 n ∑ j = 1 m [ gcd ( i , j ) = d ] ,我们发现这就是求 gcd gcd 等于 d d 的数对个数(这简直是句废话)。
又令F(d)=d|if(i),显然 F(d) F ( d ) gcd gcd 等于 d d 倍数的数对个数,那么也有F(d)=ndmd。根据莫比乌斯反演定理的第二种形式,有:
f(d)=d|iμ(id)F(i)=d|iμ(id)nimi f ( d ) = ∑ d | i μ ( i d ) F ( i ) = ∑ d | i μ ( i d ) ⌊ n i ⌋ ⌊ m i ⌋
把这个式子带进 ans a n s 那个式子,有:
ans=nd=1D(d)d|iμ(id)nimi a n s = ∑ d = 1 n D ( d ) ∑ d | i μ ( i d ) ⌊ n i ⌋ ⌊ m i ⌋
交换 d,i d , i 的位置,有:
ans=ni=1nimid|iD(d)μ(id) a n s = ∑ i = 1 n ⌊ n i ⌋ ⌊ m i ⌋ ∑ d | i D ( d ) μ ( i d )
显然我们如果预处理出 g(i)=d|iD(d)μ(id) g ( i ) = ∑ d | i D ( d ) μ ( i d ) 的前缀和,就可以用数论分块处理每个询问了。
那么现在我们考虑 a a 的限制,实际上a是在限制只有 D(i)a D ( i ) ≤ a D(i) D ( i ) g(i) g ( i ) 有贡献,因此我们把所有询问按 a a 从小到大排序,对新产生的贡献暴力在g中单点修改,因为 D(i) D ( i ) g(j) g ( j ) 产生贡献当且仅当 i|j i | j ,那么修改次数显然是 nlogn n log ⁡ n 级别的,然后要在数论分块中维护 g g 的前缀和查询,这个显然可以用常数小又好写的树状数组解决。
那么我们就解决了此题,时间复杂度为O(Qnlogn+nlog2n)。注意到这题模数很特殊,直接用int自然溢出,最后输出时对 2311 2 31 − 1 取个按位与就行了。
以下是本人代码:

#include <bits/stdc++.h>
using namespace std;
int T,p[100010];
int maxn,D[100010],mu[100010],s[100010],ans[20010];
int prime[100010];
bool vis[100010]={0};
struct query
{
    int id,n,m,a;
}q[20010];

bool cmp(query a,query b)
{
    return a.a<b.a;
}

bool cmpp(int a,int b)
{
    return D[a]<D[b];
}

int lowbit(int x)
{
    return x&(-x);
}

void add(int x,int c)
{
    for(int i=x;i<=maxn;i+=lowbit(i))
        s[i]+=c;
}

int sum(int x)
{
    int ans=0;
    for(int i=x;i;i-=lowbit(i))
        ans+=s[i];
    return ans;
}

void init()
{
    scanf("%d",&T);
    maxn=0;
    for(int i=1;i<=T;i++)
    {
        scanf("%lld%lld%lld",&q[i].n,&q[i].m,&q[i].a);
        if (q[i].n>q[i].m) swap(q[i].n,q[i].m);
        maxn=max(maxn,q[i].n);
        q[i].id=i;
    }
    sort(q+1,q+T+1,cmp);

    for(int i=1;i<=maxn;i++)
        for(int j=1;i*j<=maxn;j++)
            D[i*j]=D[i*j]+i;
    for(int i=1;i<=maxn;i++)
        p[i]=i;
    sort(p+1,p+maxn+1,cmpp);
}

void calc_mu()
{
    mu[1]=1;
    prime[0]=0;
    for(int i=2;i<=maxn;i++)
    {
        if (!vis[i])
        {
            prime[++prime[0]]=i;
            mu[i]=-1;
        }
        for(int j=1;j<=prime[0]&&i*prime[j]<=maxn;j++)
        {
            vis[i*prime[j]]=1;
            if (i%prime[j]==0)
            {
                mu[i*prime[j]]=0;
                break;
            }
            mu[i*prime[j]]=-mu[i];
        }
    }
}

int main()
{
    init();
    calc_mu();

    int nowp=1;
    for(int i=1;i<=T;i++)
    {
        while(D[p[nowp]]<=q[i].a)
        {
            for(int j=1;j*p[nowp]<=maxn;j++)
                add(j*p[nowp],D[p[nowp]]*mu[j]);
            nowp++;
        }
        int n=q[i].n,m=q[i].m;
        ans[q[i].id]=0;
        for(int j=n;j>=1;j=max(n/(n/j+1),m/(m/j+1)))
        {
            int l=max(n/(n/j+1)+1,m/(m/j+1)+1),r=j;
            ans[q[i].id]=ans[q[i].id]+(sum(r)-sum(l-1))*(n/j)*(m/j);
        }
    }

    for(int i=1;i<=T;i++)
        printf("%d\n",ans[i]&2147483647);

    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值