C - Mophues HDU - 4746 [莫比乌斯反演+分块加速+前缀和处理]

题意:定义一个数字为lucky时,当且仅当这个数字的质因数的个数<=p(输入的数字)时,问在 a[1,n],b[1,m] a ∈ [ 1 , n ] , b ∈ [ 1 , m ] 有多少对 (a,b)使gcd(a,b)=k ( a , b ) 使 得 g c d ( a , b ) = k k k 为lucky数字


题解:
我们设:
f(d)=a=1nb=1m[gcd(a,b)=1]

F(n)=n|df(d) F ( n ) = ∑ n | d f ( d )

解释一下: f(d) f ( d ) 为计算在给定区间内gcd(a,b)=d的对数,F(n)表示在给定区间内gcd为n的倍数的有多少对

由莫比乌斯反演的公式可以得到如下公式:

f(n)=x|du(dx)F(d) f ( n ) = ∑ x | d u ( d x ) F ( d )

我们可以很轻松的得到 F(d)=ndmd F ( d ) = ⌊ n d ⌋ ⌊ m d ⌋ ;

我们假设k的质因子数目不超过p,即满足题意则:

ans=kf(k) a n s = ∑ k f ( k )

F(n) F ( n ) 带入 f(n) f ( n ) 然后带入 ans a n s 中可以得到

ans=kk|nu(nk)nkmk a n s = ∑ k ∑ k | n u ( ⌊ n k ⌋ ) ⌊ n k ⌋ ⌊ m k ⌋

将上式整理可以得到

ans=k|nu(nk)knkmk a n s = ∑ k | n u ( n k ) ∑ k ⌊ n k ⌋ ⌊ m k ⌋

此时总的复杂度为总的复杂度就为 o(qnlogn+n) o ( q ∗ n ∗ l o g n + n ) ,大概获得一个TLE

我们观察上面的 ans a n s 为一次询问的复杂度,如果每次询问的复杂度为 log(n)(n) l o g ( n ) 或 者 ( n ) 还可以接受。

我们考虑如何优化加速 ans a n s 的求解

首先我们考虑 k|nu(nk) ∑ k | n u ( n k ) :

是否可以通过前缀和的形式进行优化,当然可以,我们通过预处理前缀和 sum(n)=k|nu(nk) s u m ( n ) = ∑ k | n u ( n k ) ,根据题意我们要保证 k k 的质因子数目不超过p,所以我们维护前缀和的同时增加一维来记录质因子的数目,数据范围为15e5最多有 log(500000)=19 l o g ( 500000 ) = 19 个质因子。这样我们就可以 O(1) O ( 1 ) 的得到前半部分的值,然后我们考虑后半部分能不能优化,由整数分块的知识可以得到在区间 [i,nni] [ i , ⌊ n ⌊ n i ⌋ ⌋ ] 值是相同的,所以我们后半部分的计算时间也可以优化到根号时间内来完成。这样总的时间复杂度为 O(n+log(n)+q(n)) O ( n + l o g ( n ) + q ( n ) ) 这样就不会被制裁了。

注:写的不好看,谢谢阅读了。


accode: a c c o d e :

#include <bits/stdc++.h>

using namespace  std;
typedef long long ll;
const ll inf = 0x3f3f3f3f;
typedef long double ld;
#define met(a, b) memset(a, b, sizeof(a))
#define rep(i, a, b) for(int i = a; i <= b; i++)
#define per(i, a, b) for(int i = a; i >= b; i--)
#define fi first
#define pb push_back
#define mpi pair<int, int>
#define mp make_pair
inline ll mul(ll x, ll y, ll mod) {ll res = (x * y - (ll)(ld)(x / mod * y + 1e-8) * mod); return res < 0 ? res + mod : res;}
const double PI = acos(-1.0);
const int maxn = 5e5 + 10;
const int mod = 1e9+7;
int T, k, a, b, c, d;

int mu[maxn + 10], prime[maxn + 10], cnt[maxn + 10];
bool check[maxn + 10];

void Moblus() {
    met(check,false);
    mu[1] = 1;
    int tot = 0;
    for(int i = 2; i <= maxn; i++) {
        if(!check[i]) {
            prime[tot++] = i;
            mu[i] = -1;
            cnt[i] = 1;
        }
        for(int j = 0; j < tot; j++) {
            if(i * prime[j] > maxn) break;
            check[i * prime[j]] = true;
            cnt[i * prime[j]] = cnt[i] + 1;
            if(i % prime[j] == 0) {
                mu[i * prime[j]] = 0;
                break;
            } else {
                mu[i * prime[j]] = -mu[i];
            }
        }
    }
}
ll sum[40][maxn + 10];

void init() {
    Moblus();
    met(sum, 0);
    for(int i = 1; i < maxn; i++) {
        for(int j = i; j < maxn; j += i) {
            sum[cnt[i]][j] += mu[j / i];
        }
    }
    for(int j = 1; j < maxn; j++) {
        for(int i = 1; i <= 20; i++) {
            sum[i][j] += sum[i - 1][j];
        }
    }
    for(int j = 1; j < maxn; j++) {
        for(int i = 0; i <= 20; i++) {
            sum[i][j] += sum[i][j - 1];
        }
    }
}

ll cal(int n, int m, int p) {
    if(p >= 20) printf("%lld\n", (ll)n * m);
    else {
        ll ans = 0;
        ll mx = min(n, m);
        for(int i = 1; i <= mx;) {
            ll a = n / i, b = m / i;
            ll key = min(n / a, m / b);
            ans += (sum[p][key] - sum[p][i - 1]) * a * b;

            i = key + 1;
        }
        printf("%lld\n", ans);
    }
}
int n, m, p;
int main() {
    int cas = 1;
    init();
    scanf("%d", &T);
    while(T--) {
        scanf("%d%d%d", &n, &m, &p);
        cal(n, m, p);
    }
    return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值