BZOJ2818 gcd - 欧拉函数

这种题应该格外注意“1”的问题(边界处理)
题意:给定N,求\(1 \leq x,y \leq N\)且gcd(x, y)为素数的(x,y)数对的个数
令p代表一个素数

\[gcd(x, y) = p \Rightarrow gcd(\frac{x}{p}, \frac{y}{p}) = 1 \]

于是有了一个朴素算法,枚举1 ~ N,并筛出小于等于N的素数,枚举到一个数x,再枚举一个素数p,看看x能否被p整除,若能,则所有与\(\frac{x}{p}\)互质的数都是所求数对,对答案做出贡献,并且把数对的顺序倒转也是符合题意的,所以此时x对答案的贡献就是\(\phi(\frac{x}{p}) * 2\),注意当\(\frac{x}{p}=1\)时还要减去1,因为\(\phi(1) = 1\)然后我们还把(1,1)倒转了(准确的说是(p,p)被多算了一次),所以要减去1
好的这样比较慢,考虑有什么更好的方法算贡献
发现算法的过程是不断找素数p的倍数,而且这个倍数要求小于等于N,设\(\frac{x}{p}=k\),发现这个k其实是连续的,1 ~ k的数乘p都不会超过N,所以他们的欧拉函数都对答案有贡献
那么我们预处理出\(\phi(i)\)的前缀和\(sum(i)\),只枚举素数p,设\(\frac{N}{p}=k\),对答案的贡献就是2 * sum[k] - 1

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
#include <cmath>
using namespace std;
#define debug(x) cerr << #x << "=" << x << endl;
const int MAXN = 10000000 + 10;
const int INF = 1 << 30;
typedef long long ll;
int n;  
int prime[MAXN],phi[MAXN],vis[MAXN],tot;
ll sum[MAXN],ans;
void primes(int n) {
    phi[1] = 1;
    for(int i=2; i<=n; i++) {
        if(!vis[i]) {
            phi[i] = i-1;
            prime[++tot] = i;
        }
        for(int j=1; j<=tot && i*prime[j] <= n; j++) {    
            vis[i*prime[j]] = 1;
            if(i % prime[j] == 0) {
                phi[i*prime[j]] = phi[i] * prime[j];
                break;
            }
            phi[i*prime[j]] = phi[i] * (prime[j] - 1);
        }
    }
}
int main() {
    scanf("%d", &n);
    primes(n);
    for(int i=1; i<=n; i++) {
        sum[i] = sum[i-1] + phi[i];
    }
    for(int i=1; i<=tot; i++) {
        ans += 2 * sum[n/prime[i]] - 1;
    }
    printf("%lld\n", ans);
    return 0;
}
posted @ 2018-10-30 20:07  Zolrk  阅读(108)  评论(0编辑  收藏  举报