容斥原理(模板+例题)

网上找来方便自己看,理解。

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

也可表示为
设S为有限集,
   
,则
由于
所以
两个集合的容斥关系公式:A∪B =|A∪B| = |A|+|B| - |A∩B |(∩:重合的部分)
三个集合的容斥关系公式:|A∪B∪C| = |A|+|B|+|C| - |A∩B| - |B∩C| - |C∩A| + |A∩B∩C|
详细推理如下:
1、 等式右边改造 = {[(A+B - A∩B)+C - B∩C] - C∩A }+ A∩B∩C
2、维恩图分块标记如右图图:1245构成A,2356构成B,4567构成C
3、等式右边()里指的是下图的1+2+3+4+5+6六部分:
那么A∪B∪C还缺部分7。
4、等式右边[]号里+C(4+5+6+7)后,相当于A∪B∪C多加了4+5+6三部分,
减去B∩C(即5+6两部分)后,还多加了部分4。
5、等式右边{}里减去C∩A (即4+5两部分)后,A∪B∪C又多减了部分5,
则加上A∩B∩C(即5)刚好是A∪B∪C。

常用方法有两种:递归法和二进制枚举法。

递归法是利用dfs的思想进行搜索,检索每一种方案进行容斥。

二进制枚举的方法最大的好处是能够枚举出所有元素组合的不同集合。假设一个集合的元素有m个,则对于m长的二进

制数来说就有m个1或0的位置,对于每一个1就对应一个元素。

整个二进制枚举完就是所有子集,从0到2^m就行。[0, 2^m)

以hdu 1796为例:

题意给定一个数n,数列m个数,求这小于n的数中,有多少个数满足能被这m个数中任意一个数整除。

思路:1~n之间有多少个能被x整除的数,公式为n/x,题目中要求小于n,所以(n-1)/x。

可以递归法求,需要保存中间重叠x次的最小公倍数lcm,符合题意的数有(n-1)/lcm个,利用k表示重叠的次数进

行或者加上,或者减去。

也可以用二进制枚举法,将符合条件的m个数,看作m位,每位是0或者是1,那么一共有2^m种状态,只要判断一下每

一个状态有多少个1,也就是有多少个数(重叠多少次),记为k,每一个1代表哪几个具体的数,求这几个数的最小

公倍数,然后(n-1)/lcm,  利用k的值来判断应该减去还是加上即可。

递归版本:


#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
int num[25];
int ans,tot,n,m;
void dfs(int pos,int pre_lcm,int k)
{
    for(int i=pos+1;i<tot;++i)
    {
        //int lcm=(num[i]*pre_lcm)/gcd(num[i],pre_lcm);
        int lcm = pre_lcm/__gcd(num[i], pre_lcm)*num[i];
        if(k&1) ans+=(n-1)/lcm;
        else ans-=(n-1)/lcm;
        dfs(i,lcm,k+1);
    }
}
int main()
{
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        ans=0,tot=1;
        memset(num,0,sizeof(num));
        for(int i=1;i<=m;++i)
        {
            int x;
            scanf("%d",&x);
            if(x>0&&x<n) num[tot++]=x;
        }
        dfs(0,1,1);
        printf("%d\n",ans);
    }
    return 0;
}

二进制版本:


#include <bits/stdc++.h>
using namespace std;
int n, m, x, k, tot;
int up, t, pos, lcm, ans;
int num[25];
int main()
{
    while(~scanf("%d %d", &n, &m))
    {
        tot = 1; ans = 0;
        for(int i = 1; i <= m; ++i)
        {
            scanf("%d", &x);
            if(x > 0 && x < n) num[tot++] = x;
        }
         up = (1<<(tot-1));
        for(int i = 1; i < up; ++i)
        {
            t = i, k = 0, pos = 1; lcm = 1;
            while(t)
            {
                if(t&1)
                {
                    lcm = num[pos]/__gcd(lcm, num[pos])*lcm;
                    ++k;
                }
                t >>= 1; ++pos;
            }
            if(k&1) ans += (n-1)/lcm;
            else ans -= (n-1)/lcm;
        }
        printf("%d\n", ans);
    }
    return 0;
}



对于容斥原理我们可以利用数学归纳法证明:
证明:
   
时,等式成立( 证明略)。
假设
   
时结论成立,则当
   
时,
所以当
   
时,结论仍成立。因此对任意
   
,均可使所证等式成立。 [1]  

LL Q[100010],factor[110],num;
//Q数组存放的就是右边边各项的因子数以及正负情况,factor[]存放对应对象的数目,num为有几个对象
void Divid(LL n)  //n的素因子分解,得到每项素因子的个数
{
    num = 0;
    for(LL i = 2; i*i <= n; ++i)
    {
        if(n%i==0)
        {
            while(n%i==0)
                n /= i;
            factor[num++] = i;
        }
    }
    if(n != 1)
        factor[num++] = n;
}
LL solve(LL n)  //容斥定理,求
{
    LL k,t,ans;
    t = ans = 0;
    Q[t++] = -1;
    for(LL i = 0; i < num; ++i)
    {
        k = t;
        for(LL j = 0; j < k; ++j)
            Q[t++] = -1*Q[j]*factor[i];
    }
    //A∪B∪C = A+B+C - A∩B - B∩C - C∩A + A∩B∩C
    //Q数组存放的就是A∪B∪C右边边各项的因子数以及正负情况。
    for(LL i = 1; i < t; ++i)
        ans += n/Q[i];
    //n/Q[i]累加起来就是A∪B∪C
    return ans;
}

例题:给定r,n求[1,r]内与n互素的个数有多少个?

思路:直接求解问题就是比较复杂的。所以我们还是研究这个问题是逆问题。也就是说求gcd(k,n) >= 2,在1 - n之间k有多少个 。那么我们就可以枚举n的素因子来进行求解。

int solve(int r, int n)
{
    vector<int>p;
    for (int i = 2; i * i <= n; ++i){
        if (n % i == 0){
            p.push_back(i);
            while (n % i == 0) n /= i;
        }
    }
    if (n > 1) p.push_back(n);
    int sum = 0;
    for (int S = 1; S < (1 << p.size()); ++S){
        int mult = 1, bits = 0;
        for (int i = 0; i < p.size(); ++i){
            if (S & (1 << i)){
                ++bits;
                mult *= p[i];
            }
        }
        int cur = r / mult;
        if (bits % 2 == 1) sum += cur;
        else sum -= cur;
    }
    return r - sum;
}



  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值