什么是容斥定理呢?我们先从一道题入手开始吧:问题:魔镜给小明m个数字(a1、a2 …… am)和一个整 数n,魔镜定义:如果有一个数,是这m个数字里面任意一 个数的倍数,那么这个数称为LuckyNumber。而小明会的题 数为[1,n]闭区间内LuckyNumber的数量。 (0 < m < 15) 那么请你帮小明计算一下他会的题目数。
我们怎么去计算小明会的做题数呢?我们只要找到1到n里面是所给数字的倍数就行了,可是想一下如果我们计算的会不会有重复的呢?如果重复了我们该怎么把重复的给去掉呢?这就用到我们的容斥定理了;
容斥定理:要计算几个集合并集的大小,我们要先将所有单个集合的大 小计算出来,然后减去所有两个集合相交的部分,再加回所 有三个集合相交的部分,再减去所有四个集合相交的部分, 依此类推,一直计算到所有集合相交的部分。
用这样的方法求出来的就是几个几何的并集了,而且也不会有重复的。
现在我们已经知道了求法,但是我们应该怎么去用呢?就是怎么去求几个集合的并集呢?
在这里我们介绍一种方法那就是二进制枚举法;
我们先来思考一下这个问题:如果要对n个物体进行选择,那么有多少种情况?
这个我们应该怎么做呢?每个问题只有两种情况,那就是选与不选,这就相当于十几个集合求并集,如果选的是奇数个物品那么我们应该去加的,如果选的是偶数的物品,那么我们应该是相减的,但是我们应该怎么选呢?我们要实现的目的就是每次选的时候我们呢要知道哦我们选了多少个。而且我们还要知道我们选看谁,那么该怎么办呢?这就用到二进制枚举法了,既然这个方法的名字和二进制有关那么我们肯定也要用到二进制了是吧;
假如我们有三件物品,我们会有8种选择方案,我们先把他们一一的列举出来2,看看有什么规矩没;
他们所对应的十进制数
0 0 0 0
0 0 1 1
0 1 0 2
0 1 1 3
1 0 0 4
1 0 1 5
1 1 0 6
1 1 1 7
是不是一共这8种情况,看一下这些像不像是二进制呢?那么我们先把他们看一下他们对应的十进制数,把二进制写出来之后,大家有没有发现什么规矩呢?好像这些数字就是从0到2的n次方-1是吧。那么我们我们把这些数字遍历一下是不是就把所有可以选的情况全部遍历了呢?然后我们再用一个位运算,例如6对应的是1 0 1,我们可以分别取 1,0,1那么我们就知道了我们一共选择了两个物品,选择的物品分别是第一个物品和第三个物品。
代码实现
#include<stdio.h>
int main()
{
int n;//一共n个物品;
scanf("%d",&n);
for(int i=0;i<(1<<n);i++)//从0到2的n次方-1;
{
for(int j=0;j<n;j++) //判断第j个物品有没有被选中;
printf("%d ",1&(i>>j));
puts(" ");
}
return 0;
}
/*
运行结果
3
0 0 0
1 0 0
0 1 0
1 1 0
0 0 1
1 0 1
0 1 1
1 1 1
*/
下面我们来讲解一个题,来加深大家的理解和看一下二进制枚举法的使用;
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 5Sample Output
Case #1: 5 Case #2: 10Hint
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,找一下在所给的区间中和n互质的个数,
这个题我们应该怎么做呢?首先我们应该先把n用质因子分解法把n分解成几个质数,然后再找出和n的质因子不互质,也就是是n的质因子的倍数的个数,至于区间不是从1开始的这个问题也很好解决,我们只需要看成两个区间最后再一减就行了;
下面给出AC代码;
#include<stdio.h>
#include<string.h>
using namespace std;
#define ll long long
int main()
{
ll t,m,b,n,cnt2=0;
scanf("%lld",&t);
while(t--)
{
scanf("%lld %lld %lld",&m,&b,&n);
ll a[1003];
memset(a,0,sizeof(a));
ll cnt=-1;
//质因子分解
for(ll i=2;i*i<=n;i++)
{
if(n%i==0) cnt++;
while(n%i==0)
{
a[cnt]=i;
n=n/i;
}
}
if(n!=1)
{
cnt++;
a[cnt]=n;
}
cnt++;
ll ans=0;
// for(int i=0;i<cnt;i++)
// {
// ans+=b/a[i];
// ans-=(m-1)/a[i];
// }
//cnt--;
//二进制枚举法
for(int i=1;i<(1<<cnt);i++)
{
ll sum=1; int cnt1=0;
for(int j=0;j<cnt;j++)
{
if(1&(i>>j))
{
cnt1++;//判断选中了几个东西
sum*=a[j];
}
}
if(cnt1&1)//如果是奇数就加;
{
ans+=b/sum;
ans-=(m-1)/sum;
}
else//是偶数就减;
{
ans-=b/sum;
ans+=(m-1)/sum;
}
}
cnt2++;
printf("Case #%d: ",cnt2);
printf("%lld\n",b-m+1-ans);
}
return 0;
}