Co-prime

题目链接

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.

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: 10


        
  

Hint

In 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之间与n互质的数的数目。(互质:两个数的最大公约数为1)

解题思路:

首先你应该把n的质因子求出来,因为n的质因子的倍数一定不是与n互质的数。利用容斥定理,把每个质因子及它的倍数当成一个集合。

只能选或不选,n个质因子的选法总共有多少种呢?二进制枚举告诉我们总共有2^n种,但是你必须要拿出一个质因子,所以从1开始,总共有2^n-1中选法,枚举每一种二进制数。枚举每一种二进制数的每一位,利用if(1&i>>j)找到是否选择第j个物品,如果选择,集合数+1。

然后判断集合数的奇偶,根据容斥定理偶加奇减的原则,如果是奇,则总数减去这个质因子集合,如果是偶,则总数加上这个质因子集合。

集合数的奇数倍或者偶数倍,就这道题而言,要变成两个质因子的乘积。

代码中为什么是SUM+=B/sum呢,因为如果你知道了一个质因子a,你要求在区间[1,n]内有多少是它的倍数的数,你就拿n/a,结果就是a的倍数的数量。例如,30中5的倍数的个数,30/5=6,5的倍数的数量就是6个。

因为这里是从A到B,而不是从1到B,所以当集合数为奇数时,总数要加上B/sum,再减去[1,A]中sum的倍数,也就是SUM-=(A-1)/sum。

注意代码中的SUM求得是所有质因子及其倍数的总数量,而sum求的是每个质因子或者相交集合时,几个质因子的乘积。

AC代码:

//#include <bits/stdc++.h>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>

using namespace std;

typedef long long ll;
const int maxn=105;

int prim[maxn]; //存每个N的质因子 
 
int main()
{
//	freopen("input.txt","r",stdin);

	ll A,B,t,n;
	cin>>t;
	ll th=0;//记录测试数据的序号
	while(t--)
	{
		th++;
		memset(prim,0,sizeof prim);
		
		scanf("%lld%lld%lld",&A,&B,&n);
		ll num=0;//记录质因子的个数 
		for(int i=2;i*i<=n;i++)
		{
			if(n%i==0)
			{
				num++;
				prim[num]=i;
				while(n%i==0)
				{
					n/=i;
				}
			}
		}
		if(n>1)
		{
			num++;
			prim[num]=n;
		}
		ll SUM=0;
		for(int i=1;i<(1<<num);i++)
		{
			ll cnt=0;//记录遍历每个二进制位数时的集合数 
			ll sum=1;//如果是单个集合数,表示质因子,如果是多个集合数,则是多个质因子相乘的结果 
			for(int j=0;j<num;j++)
			{
				if(1&(i>>j))
				{
					cnt++;
					sum*=prim[j+1]; 
				 } 
			}
			if(1&cnt)
			{
				SUM+=B/sum;
				SUM-=(A-1)/sum;
			}
			else
			{
				SUM-=B/sum;
				SUM+=(A-1)/sum;
			}
		}
		printf("Case #%lld: %lld\n",th,B-A+1-SUM);
	 } 
	 	
	return 0;
}

参考了这个同学的代码:


#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define met(Q,QQ) memset(Q,QQ,sizeof(Q))
int main()
{
    ll t,A,B,n,a[103];
    scanf("%lld",&t);
    ll aaa=0;
    while(t--)
    {
        aaa++;
        met(a,0);
        scanf("%lld %lld %lld",&A,&B,&n);
        ll cnt=0;
        for(int i=2;i*i<=n;i++) //质因子分解
        {
            if(n%i==0)
            {
                cnt++;
                a[cnt]=i;
            }
            while(n%i==0)
            {
                n/=i;
            }
        }
        if(n!=1)
        {
            cnt++;
            a[cnt]=n;
        }
        ll SUM=0;//SUM的目的是算所有不与n互质的数
        for(int i=1;i<(1<<cnt);i++)//二进制枚举所有可能的值从1到2^n,i从1开始,所以是从1开始枚举
        {
            ll sum=1,cnt1=0;
            for(int j=0;j<cnt;j++)//枚举的作用?枚举每一位,看是否选择了第j件物品
            {
                if(1&(i>>j))        //判断有没有选中这个集合,如何判断的?如果为1,则说明选中了这件物品
                {
                    sum*=a[j+1];    //选中的数字的乘积,a[j+1]?sum*=a[j+1],如果只有一个质因子,则就等于这个质因子,如果有多个就乘起来,表示相交,之后用B除以他们的积
                    cnt1++;;          //二进制中选中1的个数,选中这个集合的个数?
                }                       //既然0、1表示选与不选,那么如果选了就+1,表示计数
            }
            if(cnt1&1) //个数为奇数的情况,如果那几个二进制位上的1相加为奇数,那就加上呗
            {
                SUM+=B/sum;//算出从1到B中能被sum整除掉的个数,即能被质因子除尽的数或者能被相交集合的质因子的乘积除尽的数,相交集合的时候就是除以两个质因子的乘积
                SUM-=(A-1)/sum;//再减去从1到A中能被sum整除掉的个数就是从A到B中能被sum整除的个数
            }
            else//选中个数为偶数的情况
            {
                 SUM-=B/sum;//与上面的相反咯
                 SUM+=(A-1)/sum;
            }
        }//最后得到的SUM是2、3、5集合并起来的值,即所有质因子及其质因子倍数的总数
        printf("Case #%lld: %lld\n",aaa,B-A+1-SUM);
    }
    return 0;
}

// 把物品的选择与不选择想象成0和1
// 就这一题来说,就是把质因子的个数计数为n。
// 想象成有n个二进制位。第一个二进制位为2,第二个3,第三个5
// 用0,1来计数
// 0 0 1 表示选择了5
// 0 1 1 表示选择了3和5

涉及知识点:

1、左移运算符<<。

运算符左边是移位对象,右边是整型表达式,代表左移的位数,其功能是把“<<”左边的运算数的各二进制位全部左移若干位。高位丢弃,低位补0。

如果是1<<n的话,表达式的值就是2^n。

2、右移运算符与左移运算符相似。

3、按位与运算符&。

双目运算符,其功能是参与运算的两个运算对象的各对应二进制位分别进行“与”运算。只有对应的两个二进制位均为1时,结果位才为1,否则为0。参与运算的数以补码的方式出现。

给个栗子:9&(-5)

00000000 00001001  (9的二进制补码)

11111111 11111011  (-5的二进制补码)

00000000 00001001  (结果)
             1  1

发现没有^_^,只有都为1的时候才等于1

按位与运算通常用来对某些位清0或保留某些位。

就像上面的代码那样1&(i>>j), 因为1的补码是0000 0001,所以表达式就成了取i>>j的末位。

4、给出求2^n的二进制码的代码


#include<bits/stdc++.h>
using namespace std;
int main()
{
    int n;//一共n个物品;
    scanf("%d",&n);
    for(int i=0;i<(1<<n);i++)//从0到2的n次方-1;
    {
        for(int j=n-1;j>=0;j--)     //判断第j个物品有没有被选中;
           printf("%d ",1&(i>>j));//思考下为什么j到0结束呢,底部给出原因 
        printf("\n");
    }
    return 0;
}
因为i>>j是向左移j位,你每次要取i末尾的值,但是移位前的末尾值你也要取
所以索性就从0开始了 

5、容斥定理,偶加奇减,即偶数倍个集合就加,奇数倍个集合就减。

此题参考了此博客:https://blog.csdn.net/qq_42757965/article/details/99060042

上一篇:如何查询SQL Server 中 Index 的创建时间


下一篇:2020寒假集训专题一搜索G题 Gym - 101755H 题解