Co-prime
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 12553 Accepted Submission(s): 4939
Two integers are said to be co-prime or relatively prime if they have no common positive divisors other than 1 or, equivalently, if their greatest common divisor is 1. The number 1 is relatively prime to every integer.
题意分析:
给定一个数 N,您需要计算 A 和 B 之间与 N互质的整数的数量。
互质:如果两个整数除了 1 之外没有共同的正除数,则称它们互质或互质
解题思路:
一开始还准备筛法,后来发现真是关公门前耍大刀......蠢!!!
题目给的A,B范围1到1e15,很显然你硬筛是行不通的。
换种思路,如果我们把A到B之间和N都不互质的数值求出来,那么B-A+1-这个数值就是答案。
我们知道一个数的质因数的倍数都不与它本身互质
例如:
A为1,B为20,N为6.
那么N的质因子通过质因子分解:2,3;
显然(1,20)中所有为因子的倍数都不互质,2的倍数(2,4,6,8,10,12,14,18,16)= 20 / 2 = 10; 3的倍数(3,6,9,12,15,18)= 20 / 3;
很明显不互质的答案并不是直接相加,因为里面有重复的,这时候就用到了我们的容斥定理,也就是n / 2 + n / 3 - n / ( 2 * 3 ),
我们发现当除数是奇数的时候加,当除数是偶数的时候减,将重复的排除掉,再次相加就得到了所有不互斥的答案,我们离成功只差一步。
但是我们该怎样完全不重不漏的找到所有质因子的倍数呢?
其实从上面推导出的公式我们可以看出来,要找所有因子的倍数,就可以将已经得到的质因子组合起来,无论两两还是三三四四组合,他们的倍数都将是质因子的倍数
现在问题有转换为了怎么求出已有的质因子所有的组合数?
这里给出有两种方法:
1. 位运算
我们将所有计算倍数的情况写出来
001(2的倍数),010(3的倍数),011(2*3,6的倍数),100(5的倍数),101(5*2,10的倍数),110(5*3,15的倍数),111(2*3*5,30的倍数)
很容易地发现,二进制表示中1的个数为奇数的情况要加,偶数情况要减。
2. 队列数组
这个其实就是硬算,将所有情况都硬算一遍,让所有的质因子每次都和组合出来的新数字再组合一遍。个人感觉队列数组结合代码是比较好理解的。
下面可以根据代码再理解一下:
// 1. 位运算
#include <iostream>
#include <cstring>
typedef long long ll;
using namespace std;
ll cnt, primes[10100], que[100005];
void DecPrimes(ll x)// 分解质因数
{
cnt = 0;
for(int i = 2; i * i <= x; i ++)
{
if(x % i == 0)
{
primes[cnt ++] = i;
while(x % i == 0) x /= i;// 避免出现质因数是另一个质因数得倍数
}
}
if(x != 1) primes[cnt ++] = x;
}
ll Class(ll m)
{
ll res = 0, ans = 0;
for(int i = 1; i < ll(1 << cnt); i ++)
{
res = 1;
ll flag = 0;
for(ll j = 0; j < cnt; j ++)// 遍历所有位置,找1
{
if(i & (ll(1 << j)))// 出现因子 因为j是某个位置,所以为1 << j
{
flag ++;
res *= primes[j];
}
}
if(flag & 1)
ans += m / res;
else
ans -= m / res;
}
return ans;
}
int main()
{
int t, C = 1;
cin >> t;
while(t --)
{
memset(primes, 0, sizeof primes);
ll a, b, n;
cin >> a >> b >> n;
DecPrimes(n);
printf("Case #%d: %lld\n",C ++, b - Class(b) - (a - 1 - Class(a - 1)));
}
}
// 2.队列运算
#include <iostream>
#include <cstring>
#define ll long long
using namespace std;
ll cnt, primes[10100], que[100005];
void DecPrimes(ll x)// 分解质因数
{
cnt = 0;
for(int i = 2; i * i <= x; i ++)
{
if(x % i == 0)
{
primes[cnt ++] = i;
while(x % i == 0) x /= i;// 避免出现质因数是另一个质因数得倍数
}
}
if(x != 1) primes[cnt ++] = x;
}
ll Class(ll m)
{
ll k, front = 0, sum = 0;
que[front ++] = -1;// que[1]一开始是为 -1,因为要符合 “奇加偶减”
for(int i = 0; i < cnt; i ++)
{
k = front;// 记录上次有多少个组合数
for(ll j = 0; j < k; j ++)// 这个质因数和前面的组合全部 ×一边
que[front ++] = que[j] * primes[i] * -1;// 容斥定理“奇加偶减”
}
for(ll i = 1; i < front; i ++)// 得到所有因子
sum += m / que[i];
return sum;
}
int main()
{
int t, C = 1;
cin >> t;
while(t --)
{
ll a, b, n;
cin >> a >> b >> n;
DecPrimes(n);
printf("Case #%d: %lld\n",C ++, b - Class(b) - ((a - 1) - Class(a - 1)));
}
}