容斥定理以及hdu4135

定义
在计数时,必须注意没有重复,没有遗漏。为了使重叠部分不被重复计算,人们研究出一种新的计数方法,这种方法的基本思想是:先不考虑重叠的情况,把包含于某内容中的所有对象的数目先计算出来,然后再把计数时重复计算的数目排斥出去,使得计算的结果既无遗漏又无重复,这种计数的方法称为容斥原理。
在这里插入图片描述

解释
看定义其实能够读懂一些容斥定理是什么意思了,但是具体和生活中有什么联系呢?
举两个例子,
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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值