您的位置:首页 > 其它

[HDU2588]GCD 欧拉函数

2017-08-24 21:09 260 查看
题目链接:Hdu2588

—————————————-

概述

题目大意如下。

给定两个正整数n和m,(2≤n≤1000000000,1≤m≤n),问有多少个x满足1≤x≤n 且 gcd(n,x)≥m。题目有多组数据。

—————————————-

题解

将题目待求的答案设为Ans,则:Ans=∑i=1n[gcd(n,i)≥m].

我们发现,上式能产生贡献的i均大于m,能产生贡献的gcd(n,i)均为n的约数。

所以我们可以只枚举n大于等于m的约数d,再判断1~n中有多少个数与n的最大公约数为d。

即:Ans=∑m≤d≤n, d|n∑i=1n [gcd(n,i)=d].

这时我们发现,中括号里的表达式值为1时,i一定是d的倍数,所以我们进一步转化:∑m≤d≤n, d|n∑i=1n [gcd(n,i)=d]= ∑m≤d≤n, d|n∑i=1n[gcd(nd,id)=1]×[d|i].

由于i一定是d的倍数,所以id的取值范围是1~nd,所以上式中∑ni=1[gcd(nd,id)=1]×[d|i] 实际上就是在统计1~nd中与nd互质的数的个数,其实就是φ(nd).

至此,我们将式子转化成:∑m≤d≤n, d|n∑i=1n[gcd(nd,id)=1]×[d|i]=∑m≤d≤n, d|nφ(nd).

只需要O(n√)枚举满足条件的约数d,再O(n√−−−√)计算φ(nd)即可,总复杂度O(n√⋅n√−−−√).

—————————————-

代码

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#define ll long long
#define For(i,j,k) for(register ll i=j; i<=(ll)k; ++i)
#define Forr(i,j,k) for(reggister ll i=j; i>=(ll)k; --i)
#define INF 0x3f3f3f3f
using namespace std;

ll T, n, m, phi, Ans;

inline ll get_phi(ll x){
ll back = x;
for(register ll i=2; i*i <= x; ++i)
if(x%i == 0){
back = back/i*(i-1);
while(x%i == 0)
x /= i;
}
if(x != 1)
back = back/x*(x-1);

return back;
}//质因数分解求欧拉函数。

int main(){
scanf("%lld", &T);
while(T --){
scanf("%lld%lld", &n, &m);

Ans = 0;
for(ll di=1; di*di<=n; ++di)
if(n%di == 0){//枚举约数n/d。
ll d = n/di; //计算d。

if(d < m)   break;//考虑到我们得到的d是单调递减的,假如当前d不满足条件,直接退出。

phi = get_phi(di);
Ans += phi;//计算答案

if(d*d == n)    continue;//特判d为根号n的情况。

if(di >= m){
phi = get_phi(d);
Ans += phi;//假如n/d也满足条件,计算答案。
}
}

printf("%lld\n", Ans);
}
return 0;
}


—————————————-

小结

本题重点在于gcd和欧拉函数之间的转化,将枚举gcd(n,i)=d 转变成枚举与nd互质的数是关键,偷换概念也是很实用的方法。

—————————————-

——wrote by miraclejzd
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: