定义
在计数时,必须注意没有重复,没有遗漏。为了使重叠部分不被重复计算,人们研究出一种新的计数方法,这种方法的基本思想是:先不考虑重叠的情况,把包含于某内容中的所有对象的数目先计算出来,然后再把计数时重复计算的数目排斥出去,使得计算的结果既无遗漏又无重复,这种计数的方法称为容斥原理。
解释
看定义其实能够读懂一些容斥定理是什么意思了,但是具体和生活中有什么联系呢?
举两个例子,
1)小学奥数题,某校六⑴班有学生45人,每人在暑假里都参加体育训练队,其中参加足球队的有25人,参加排球队的有22人,参加游泳队的有24人,足球、排球都参加的有12人,足球、游泳都参加的有9人,排球、游泳都参加的有8人,问:三项都参加的有多少人?容斥定理就能解决这样的问题。
2)1000里面能不能整除3或5的数字?
我们可以先找到能够整除3的数字,是1000/3 = 333…1,所以就是333个
1000里面能整除5的数字个数为1000/5=200
那我们又知道这里肯定是计算重复了,因为整除3的里面包含整除5的,整除5的里面包含整除3的,所以这部分数字我们实际上加了两遍。这部分数字就是能够整除5和3最小公倍数的一类数。我们同样求出来有多少,1000/15=66…10,所以就是66个。
那么整除3或5的数字为:333+200-66
不能整除就是1000-(333+200-66)
例题
hdu4135
题意:给一个A和B还有N,求出在A和B之间有多少个数字是和N互质的。
思路
这就是一个标准的用容斥定理的题。
如果每次都暴力然后gcd就有点浪费时间了,我们可以换个思路,先找到区间和n不互质的,然后在用总数减去不互质的数量就是互质的数量。而要求与n非互质的整数的个数,可以先找到n的所有质因子,以便于得到所有的非互质的数字,然后在运用容斥定理进行计算。
代码实现:
#include <iostream>
using namespace std;
typedef long long ll;
ll a[1000], num = 0;
void find(ll n) //查找n的所有质因数,并储存在a数组里面
{
num = 0;
for (ll i = 2; i*i<=n; i++) //i只需要到sqrt(n)即可
{
if (n%i == 0)
{
a[num++] = i;
}
while (n%i == 0) //比如16里面含有许多2这个质数,而质数的倍数都不是质数,所以就一直除,知道n里面不含这个质因子
{
n/=i;
}
}
if (n > 1) //除完n不为1,说明n本身也是质数
{
a[num++] = n;
}
}
ll slove(ll m)
{
ll sum = 0, que[1000], t = 0;
que[t++] = -1; //初始值设置为-1
for (int i=0; i<num; i++)
{
ll k = t;
for (int j=0; j<k; j++)
{
que[t++] = que[j]*a[i]*-1; //为了排除重复元素,比如2和3,其实里面包含了6,所以要减去一个6,这就是容斥定理
}
}
for (int i=1; i<t; i++)
{ //这里就是上面例二的想法
sum+=m/que[i];
}
return sum;
}
int main()
{
ios::sync_with_stdio(false);
int t, q = 1;
cin >> t;
while (t--)
{
ll x, y, n;
cin >> x >> y >> n;
find(n);
cout << "Case #" << q++ << ": ";
cout << y-slove(y)-(x-1-slove(x-1)) << endl;
//前面y-slove(y)是[1, y]互质总数,应该在减去[1, x-1]内的互质数量,这样才是[x, y]区间的。
}
return 0;
}
拓展
其实容斥定理还有两种是实现方法,上面的是队列法,还有递归和位操作。会一种就可以的。
递归:
#include<bits/stdc++.h>
using namespace std;
int a[]={2,3,5};
int b=600;
int sum=0;
int n=3;
void dfs(int i,int num,int x,int mu){ //i表示第几个元素,num表示现在一共用了几个,x表示最多能用几个,mu表示当前取了的数的乘积
if(num==x){
sum+=b/mu; //b就表示600
return ;
}
if(i==n) return; //一共有n个,比如上面的2,3,5,一共有3个,
dfs(i+1,num+1,x,mu*a[i]); //a中存储的就是2,3,5,然后取或者不取
dfs(i+1,num,x,mu);
}
int rong(){
int s=0;
for(int i=1;i<=n;i++){
sum=0;
dfs(0,0,i,1);
if(i&1) s+=sum; //容斥定理
else s-=sum;
}
return s; //s为能被2,3,5整除的数的个数。
}
void dfs(int i,int mu,int num){
mu*=a[i];
if(num%2) sum+=b/mu;
else sum-=b/mu;
for(int j=i+1;j<3;j++){
dfs(j,mu,num+1);
}
}
int main(){
printf("%d\n",rong());
sum=0;
for(int i=0;i<3;i++){
dfs(i,1,1);
}
printf("%d\n",sum);
return 0;
}
位操作:
#include<bits/stdc++.h>
using namespace std;
int p[]={2,3,5};
int cnt=3;
int cal(int n=600){
int res=0;
for(int i=1;i<(1<<cnt);i++){
int t=i,tmp=1,k=0;
int len=0;
while(t){
if(t&1){
tmp*=p[k];
len++;
}
t>>=1;
k++;
}
if(len&1) res+=n/tmp;
else res-=n/tmp;
}
return res;
}
int main(){
cout<<cal()<<endl;
return 0;
}