hdu4746-莫比乌斯反演+交换求和顺序+预处理

https://vjudge.net/contest/386727#problem/D

如果你在csdn上查本题的其他题解,你会很恼火,因为你不知道他们的F函数是怎么来的。我这篇题解将会把F函数的来历,这个唯一的关键点讲清楚!

不妨设n<=m。做这一假设是因为gcd值的范围为1~n。

考虑到只有gcd(i,j)==val这种形式的式子才能用莫比乌斯反演处理,我们不妨就加多一层求和符号,并增加限制。
a n s = ∑ 1 < = x < = n [ p f [ x ] < = k ] ∑ 1 < = i < = n ∑ 1 < = j < = m [ g c d ( i , j ) = = x ] ans = \sum_{1<=x<=n} [pf[x] <= k] \sum_{1<=i<=n} \sum_{1<=j<=m} [gcd(i,j) == x] ans=1<=x<=n[pf[x]<=k]1<=i<=n1<=j<=m[gcd(i,j)==x]
里面的两层求和符号是模板题,直接摆结论了。
a n s = ∑ 1 < = x < = n [ p f [ x ] < = k ] ∑ 1 < = d < = n / x μ ( d ) n x ∗ d m x ∗ d ans = \sum_{1<=x<=n} [pf[x] <= k] \sum_{1<=d<=n/x} \mu(d) \frac{n}{x*d}\frac{m}{x*d} ans=1<=x<=n[pf[x]<=k]1<=d<=n/xμ(d)xdnxdm
预处理前缀和(注:k范围是1~18,因为全部取2就是上限。)+整除分块,直接求这个式子的复杂度应该不到O(n)(因为我不是很清楚),但也会TLE。

考虑交换求和顺序继续寻找预处理机会。设T=x*d
a n s = ∑ 1 < = x < = n [ p f [ x ] < = k ] ∑ 1 < = T < = n μ ( T x ) ∗ n T ∗ m T ∗ [ x ∣ T ] ans=\sum_{1<=x<=n}[pf[x]<=k]\sum_{1<=T<=n}\mu(\frac Tx)*\frac nT * \frac mT * [x|T] ans=1<=x<=n[pf[x]<=k]1<=T<=nμ(xT)TnTm[xT]
T摆脱了束缚,可以放最外层。
a n s = ∑ 1 < = T < = n n T ∗ m T ∑ x ∣ T [ p f [ x ] < = k ] ∗ μ ( T x ) ans=\sum_{1<=T<=n}\frac nT * \frac mT\sum_{x|T}[pf[x]<=k]*\mu(\frac Tx) ans=1<=T<=nTnTmxT[pf[x]<=k]μ(xT)

F ( k , T ) = ∑ x ∣ T [ p f [ x ] < = k ] ∗ μ ( T x ) F(k,T)=\sum_{x|T}[pf[x]<=k]*\mu(\frac Tx) F(k,T)=xT[pf[x]<=k]μ(xT)

预处理所有的 F(k,T) 即可(代码里的数组sum)。为了方便,我们先固定pf[x]==k,求完以后最后遍历一遍数组求前缀和。我们枚举每个k(代码里的变量x),则k固定后,可以枚举倍数来求贡献。

re_(x,0,D){
    rep(i,1,lim){
        if(pf[i] != x) continue;
        for(int j = i;j <= lim;j += i)
            sum[x][j] += mu[j/i];
    }
}

完整代码

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
#define rep(i,a,b) for(int i = (a);i <= (b);++i)
#define re_(i,a,b) for(int i = (a);i < (b);++i)
#define dwn(i,a,b) for(int i = (a);i >= (b);--i)

const int N = 5e5 + 5,D = 19;

int n,m,k,pf[N],sum[D][N];
bool vis[N];vector<int> primes;int mu[N];

template<typename Type>inline void read(Type &xx){
    Type f = 1;char ch;xx = 0;
    for(ch = getchar();ch < '0' || ch > '9';ch = getchar()) if(ch == '-') f = -1;
    for(;ch >= '0' && ch <= '9';ch = getchar()) xx = xx * 10 + ch - '0';
    xx *= f;
}

void init_primes(int lim){
    vis[1] = true;pf[1] = 0;mu[1] = 1;
    rep(i,2,lim){
        if(!vis[i]) primes.push_back(i),pf[i] = 1,mu[i] = -1;
        for(int &p: primes){
            if(i > lim / p) break;
            vis[i * p] = true;
            pf[i * p] = pf[i] + 1;
            if(i % p == 0) break;
            mu[i * p] = -mu[i];
        }
    }
    re_(x,0,D){
        rep(i,1,lim){
            if(pf[i] != x) continue;
            for(int j = i;j <= lim;j += i)
                sum[x][j] += mu[j/i];
        }
    }
    re_(x,0,D){
        rep(i,1,lim) sum[x][i] += sum[x][i-1];
    }
    rep(i,1,lim){
        re_(x,1,D) sum[x][i] += sum[x-1][i];
    }
}

int main(int argc, char** argv) {
    init_primes(N-5);
    int T;read(T);
    while(T--){
        read(n);read(m);read(k);
        k = min(k,D-1);
        int lim = min(n,m);
        LL ans = 0;
        for(int L = 1;L <= lim;){
            int R = min(n/(n/L),m/(m/L));
            ans += 1LL*(n/L)*(m/L)*(sum[k][R]-sum[k][L-1]);
            L = R+1;
        }
        printf("%lld\n",ans);
    }
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值