Co-prime
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)
Problem Description
Given a number N, you are asked to count the number of integers between A and B inclusive which are relatively prime to N.
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.
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.
Input
The first line on input contains T (0 < T <= 100) the number of test cases, each of the next T lines contains three integers A, B, N where (1 <= A <= B <= 10
15) and (1 <=N <= 10
9).
Output
For each test case, print the number of integers between A and B inclusive which are relatively prime to N. Follow the output format below.
Sample Input
2 1 10 2 3 15 5
Sample Output
Case #1: 5 Case #2: 10HintIn the first test case, the five integers in range [1,10] which are relatively prime to 2 are {1,3,5,7,9}.
解题思路:这道题目如果去从a遍历到b,那肯定会TLE,所以这里可以用巧妙点的方法避开枚举。之前在《算法竞赛入门经典》里面看到过这样的问题:小于n且与n互为素数的个数。它的原理是利用容斥原理,那么这个题目的解题思路也是利用容斥原理。关键是如何找到a到b这个区间的数,这个地方确实不太容易想,其实可以先算出[1,b]这个区间内的,再算出[1,a-1]这个区间内的,然后两者相减,就能去掉重叠的部分,算出的就是[a,b]这个区间内与n互为素数的个数。首先当然是把n分解质因数,然后将其保存起来。其中容斥原理的算法可以利用dfs去算。
但是不知道为什么一直WA。。。
我的代码:
#include<iostream>
#include<cmath>
#include<vector>
using namespace std;
vector<int> v;
void primeFactor(int n)
{
int q = sqrt(n+0.5);
v.clear();
for(int i = 2; i <= q; i++)
{
if(n % i == 0)
{
v.push_back(i);
while(n % i == 0)
n = n / i;
}
}
if(n > 1) v.push_back(n);
}
__int64 dfs(int cur,int b,int n)
{
__int64 res = 0;
if(b == 0) return 0;
for(int i = cur; i < v.size(); i++)
res += b / v[i] - dfs(i+1,b/v[i],n);
return res;
}
int main()
{
int t,cas = 1;
cin>>t;
while(t--)
{
__int64 a,b,n;
cin>>a>>b>>n;
primeFactor(n);
int ans1 = b - dfs(0,b,n);
int ans2 = a-1 - dfs(0,a-1,n);
cout<<"Case #"<<cas++<<": "<<ans1-ans2<<endl;
}
return 0;
}
补充一下:关于容斥原理的算法又学到一招:队列数组。
我们可以用一个队列(数组也行)存储出现的分母,我们可以令队列的第一个元素为1,让每次出现的m的因子和队列中的元素一个一个相乘再存储到队列中,最后就会发现存储的元素就是我们上面的分母了。现在的问题又变成了我们时候用加什么时候用减,这里我们只需要每次存的时候在再乘一个(-1),就可以得到我们想要的结果了。
以这道题为例:
__int64 cal(__int64 n, __int64 t) //表示从[1,t]的区间内,有多少个数与n互为素数
{
int num=0;
que[num++]=1; //队列数组,里面存放的是各个倍数的乘积
for(int i=0; i<vt.size(); i++) //vt[i]存放的是第i个质因子
{
int ep=vt[i];
int k=num;
for(int j=0; j<k; j++)
que[num++]=ep*que[j]*(-1); //乘以-1,可以保证“奇负偶正”
}
__int64 sum=0;
for(int i=0; i<num; i++)
sum+=t/que[i]; // t/que[i]表示在区间[1,t]中间,因子为que[i]的有多少个
return sum;
}
其实这个方法感觉很像《算法竞赛入门经典》里面的那个欧拉函数的变形式。