[BZOJ3529][Sdoi2014]数表(莫比乌斯反演+树状数组)

211 篇文章 0 订阅
33 篇文章 0 订阅

题目描述

传送门

题解

md刚开始读错题了
本来不是很难的一道题被我搞的看起来不可能做出来?

首先看看数表里的数都是啥
实际上位置(i,j)上的数就是 f(gcd(i,j)) ,其中 f(i) 表示i的约数和
那么考虑一下怎么科学地求出来 f
约数和定理:
n=ipkii,其中 pi 为n的质因子, ki 为质因子次数(正整数)
那么 n 的所有约数的和为f(n)=i(j=0kipji)
可以发现当 (a,b)=1 f(ab)=f(a)f(b) ,那么 f 是一个积性函数
考虑线性筛,显然当p为质数时 f(p)=1+p ,当 (a,b)=1 f(ab)=f(a)f(b)
那么现在的问题是在原先的数上多加一个已有的质因子,也就是用 f(a) p 推出f(ap),其中 a mod p=0
同样还是要根据约数和公式。我们可以记录一个 g(i) ,表示i除去最小质因子剩下的数。
然后令 a=ipkii p=p1 那么

f(a)=(p01+p11+...+pk11)(p02+p12+...+pk22)...(p0m+p1m+...+pkmm)

f(ap)=(p01+p11+...+pk1+11)(p02+p12+...+pk22)...(p0m+p1m+...+pkmm)

观察这两个式子,可以得出: f(ap)=f(a)p+f(g(a))
这样我们就可以在 O(n) 的时间里得到 f

如果不考虑a的限制的话,实际上就是求

i=1nj=1mf(gcd(i,j))

然后可以画一波柿子
i=1nj=1md=1n[(i,j)=d]f(d)

d=1ni=1ndj=1md[(i,j)=1]f(d)

然后利用反演公式 [n=1]=d|nμ(d)
d=1ni=1ndj=1mdt|(i,j)μ(t)f(d)

d=1nt=1ndi=1nd[t|i]j=1md[t|j]μ(t)f(d)

d=1nt=1ndndtmdtμ(t)f(d)

然后令 T=dt ,现在 d T的约数
T=1nnTmTd|Tμ(Td)f(d)

发现 μ f 都是积性函数所以再搞一波线性筛就能求出卷积来了?
那你没发现没法满足a的性质了嘛?

考虑如何将a的限制插进去
实际上就是只有f(d)a的是有价值的
那么可以考虑离线之后将a排序,然后再将 f 排序,然后对于每一个a,将f(d)a f 都加入
至于怎么插入…我的方法是…暴力…
也就是对于一个d,枚举其n范围内的每个倍数,μ f 都可以提前筛出来
类似于埃氏筛法的复杂度分析?O(nloglogn)?不是很清楚大概比 O(n) 大一点
因为分块求的时候是 f 的前缀和,所以搞一个bit维护一下就行了
时间复杂度大约O(n+Qlogn(n+m))
注意直接自然溢出就行了

代码

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
#define N 100000
#define LL long long

int T;
int p[N+3],prime[N+3],mu[N+3],g[N+3],f[N+3],C[N+3],ans[N+3];
struct Q{int n,m,a,id;}q[N+3];
struct D{int val,id;}d[N+3];

void get()
{
    mu[1]=1;f[1]=1;g[1]=1;
    for (int i=2;i<=N;++i)
    {
        if (!p[i])
        {
            prime[++prime[0]]=i;
            mu[i]=-1;
            g[i]=1;
            f[i]=i+1;
        }
        for (int j=1;j<=prime[0]&&i*prime[j]<=N;++j)
        {
            p[i*prime[j]]=1;
            if (i%prime[j]==0)
            {
                mu[i*prime[j]]=0;
                g[i*prime[j]]=g[i];
                f[i*prime[j]]=f[i]*prime[j]+f[g[i]];
                break;
            }
            else
            {
                mu[i*prime[j]]=-mu[i];
                g[i*prime[j]]=i;
                f[i*prime[j]]=f[i]*f[prime[j]];
            }
        }
    }
    for (int i=1;i<=N;++i) d[i].val=f[i],d[i].id=i;
}
void add(int loc,int val)
{
    for (int i=loc;i<=N;i+=i&-i)
        C[i]+=val;
}
int query(int loc)
{
    int ans=0;
    for (int i=loc;i>=1;i-=i&-i)
        ans+=C[i];
    return ans;
}
int cmpq(Q x,Q y)
{
    return x.a<y.a;
}
int cmpd(D x,D y)
{
    return x.val<y.val;
}
int calc(int n,int m)
{
    int ans=0;
    if (n>m) swap(n,m);
    for (int i=1,j=0;i<=n;i=j+1)
    {
        j=min(n/(n/i),m/(m/i));
        ans+=(n/i)*(m/i)*(query(j)-query(i-1));
    }
    return ans;
}
int main()
{
    get();
    sort(d+1,d+N+1,cmpd);
    scanf("%d",&T);
    for (int i=1;i<=T;++i) scanf("%d%d%d",&q[i].n,&q[i].m,&q[i].a),q[i].id=i;
    sort(q+1,q+T+1,cmpq);
    int now=1;
    while (d[now].val<0) ++now;
    for (int i=1;i<=T;++i)
    {
        while (now<=N&&d[now].val<=q[i].a)
        {
            for (int j=1;j*d[now].id<=N;++j)
                add(j*d[now].id,mu[j]*d[now].val);
            ++now;
        }
        ans[q[i].id]=calc(q[i].n,q[i].m);
    }
    for (int i=1;i<=T;++i)
    {
        ans[i]&=2147483647;
        printf("%d\n",ans[i]);
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值