[洛谷 P4318] 完全平方数 (杜教筛)

题意

T T T 组询问,回答第 K i K_i Ki 个不是完全平方数的正整数倍的数。

1 ≤ K i ≤ 1 0 9 , T ≤ 50 1\le K_i \le 10^9,T \le 50 1Ki109,T50

分析:

法一:

如果一个数 n n n 不是完全平方数,那么 n = p 1 α 1 p 2 α 2 ⋯ p k α k n=p_1^{\alpha_1}p_2^{\alpha_2} \cdots p_k^{\alpha_k} n=p1α1p2α2pkαk 0 ≤ α i ≤ 1 0 \le \alpha_i \le 1 0αi1,所以就想到了莫比乌斯函数,那么题目要询问第 K K K 个数是什么,可以用二分来解决,但是必须要有单调性,莫比乌斯函数前缀和可能存在负数,所以就想到把莫比乌斯函数做一个平方,这样前缀和就没有负数了,就有了单调性。

现在考虑如何计算 ∑ i = 1 n μ 2 ( i ) \sum\limits_{i=1} ^{n} \mu^2(i) i=1nμ2(i),根据数据范围来看必须要用杜教筛来快速求前缀和,设 f ( n ) = μ 2 ( n ) f(n)=\mu^2(n) f(n)=μ2(n),那么设 g ( n ) = [ n = k 2 , k ∈ N + ] g(n)=[n=k ^ 2,k \in N^+] g(n)=[n=k2,kN+],发现 f ∗ g = 1 f*g=1 fg=1,所以

S ( n ) = n − ∑ i = 2 n g ( i ) S ( ⌊ n i ⌋ ) S(n)=n-\sum_{i=2}^{n}g(i)S(\lfloor \frac{n}{i} \rfloor) S(n)=ni=2ng(i)S(in)

改为枚举平方

S ( n ) = n − ∑ i = 2 n S ( ⌊ n i 2 ⌋ ) S(n)=n-\sum_{i=2}^{\sqrt{n}}S(\lfloor \frac{n}{i^2} \rfloor) S(n)=ni=2n S(i2n)

代码( O 2 O_2 O2优化):

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e6 + 5;
unordered_map<int,int> mp;
int T, n, mobius[N], primes[N], cnt, sum[N];
bool st[N];
void get_mobius(int n) {
    mobius[1] = 1;
    for (int i = 2; i <= n; i ++) {
        if (!st[i]) {
            primes[cnt ++] = i;
            mobius[i] = -1;
        }
        for (int j = 0; primes[j] * i <= n; j ++) {
            int t = primes[j] * i;
            st[t] = 1;
            if (i % primes[j] == 0) {
                mobius[t] = 0;
                break;
            }
            mobius[t] = -mobius[i];
        }
    }
    for (int i = 1; i <= n; i ++) sum[i] = sum[i - 1] + mobius[i] * mobius[i];
}
int Sum(int n) {
    if (n < N) return sum[n];
    if (mp[n]) return mp[n];
    int res = n;
    for (int l = 2, r; l * l <= n; l = r + 1) {
        r = n / (n / l);
        res -= Sum(n / (l * l));
    }
    return mp[n] = res;
}
signed main() {
    get_mobius(N - 1);
    cin >> T;
    while (T --) {
        cin >> n;
        int l = 1, r = n << 1;
        while (l < r) {
            int mid = l + r >> 1;
            if (Sum(mid) < n) {
                l = mid + 1;
            } else {
                r = mid;
            }
        }
        cout << l << endl;
    }
}

法二:

∑ i = 1 n μ 2 ( i ) = ∑ i = 1 n ∑ d 2 ∣ i μ ( d ) = ∑ d = 1 n μ ( d ) ⌊ n d 2 ⌋ \sum_{i=1}^{n} \mu^2(i)=\sum_{i=1} ^{n}\sum_{d^2 \mid i} \mu(d)=\sum_{d=1} ^{\sqrt{n}} \mu(d)\lfloor \frac{n}{d^2}\rfloor i=1nμ2(i)=i=1nd2iμ(d)=d=1n μ(d)d2n

代码:

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e6 + 5;
int T, n, mobius[N], primes[N], cnt, sum[N];
bool st[N];
void get_mobius(int n) {
    mobius[1] = 1;
    for (int i = 2; i <= n; i ++) {
        if (!st[i]) {
            primes[cnt ++] = i;
            mobius[i] = -1;
        }
        for (int j = 0; primes[j] * i <= n; j ++) {
            int t = primes[j] * i;
            st[t] = 1;
            if (i % primes[j] == 0) {
                mobius[t] = 0;
                break;
            }
            mobius[t] = -mobius[i];
        }
    }
    for (int i = 1; i <= n; i ++) sum[i] = sum[i - 1] + mobius[i];
}
int Sum(int n) {
    int res = 0;
    for (int l = 1, r; l * l <= n; l = r + 1) {
        r = n / (n / l);
        res += (sum[r] - sum[l - 1]) * (n / (l * l));
    }
    return res;
}
signed main() {
    get_mobius(N - 1);
    cin >> T;
    while (T --) {
        cin >> n;
        int l = 1, r = n << 1;
        while (l < r) {
            int mid = l + r >> 1;
            if (Sum(mid) < n) {
                l = mid + 1;
            } else {
                r = mid;
            }
        }
        cout << l << endl;
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值