容斥原理(模板+例题)

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

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

也可表示为
设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
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
第一章 引论 1.1 组合数学研究的对象 1.2 组合问题典型实例 1.2.1 分派问题 1. 2.2 染色问题 1.2.3 幻方问题 1.2.4 36军官问题 1.2.5 中国邮路问题 习 题 第二章 排列与组合 2.1 两个基本计数原理 2.2 无重集的排列与组合 2.3 重集的排列与组合 2.4 排列生成算法 2.4.1 序数法 2.4.2 字典序法 2.4.3 轮转法 2.5 组合生成算法 .2.6 应用举例 习 题 第三章 容斥原理 3.1 引 言 3.2 容斥原理 3.3 几个重要公式 3.4 错位排列 3.5 有限制的排列 3.6 棋阵多项式 3.7 禁位排列 习 题 第四章 鸽巢原理 4.1 鸽巢原理 4. 2 鸽巢原理的推广形式 4. 3 ramsey数 4.4 ramsey数的性质 4.5 ramsey定理 习 题 第五章 母函数 5.1 母函数概念 5.2 幂级数型母函数 5.3 整数的拆分 5.4 ferrers图 5.5 指数型母函数 习 题 第六章 递归关系 6.1 引言 6.2 几个典型的递归关系.. 6.3 用母函数方法求解递归关系 6.4 常系数线性齐次递归关系的求解 6.5 常系数线性非齐次递归关系的求解 6.6 非常系数非线性递归关系的求解 6.7 差分表法 6.8 stirling数 习 题 第七章 polya定理 7.1 有限集的映射 7.2 群的基本概念 7.3 置换群 7.4 置换的奇偶性 7.5 置换群下的共轭类 7.6 burnside引理 7.7 polya定理 7.8 polya定理的母函数型式 7.9 不标号图的计数 习 题 第八章 图论基础 8.1 图的基本概念 8.2 同构图、完全图与二分图 8.3 通路、回路与图的连通性 8.4 euler图与hamilton图 8.5 割集与树 8.6 图的矩阵表示法 8.7 平面图、对偶图与色数 8.8 匹配理论 8.9 网络流 习 题 第九章 拉丁方与区组设计 9.1 引言 9.2 拉丁方 9.3 有限域 9.4 正交拉丁方的构造 9.5 完全区组设计 9.6 平衡不完全区组设计(bibd) 9.7 区组设计的构造 9.8 steiner三连系 9.9 hadamard矩阵 习 题 第十章 线性规划 10.1 lp问题引例 10.2 lp问题的一般形式 10.3 lp问题的标准型 10.4 可行域和最优可行解 10.5 单纯形法 10.6 单纯形表格法 10.7 两阶段法 10.8 对偶原理 10.9 对偶单纯形法 10.10 应用举例 习 题 第十一章 组合优化算法与计算的时间复杂度理论 11.1 dijkstra算法 11.2 floyd算法 11.3 kruskal算法 11.4 求最优树的破圈法和统观法 11.5 二分图中最大匹配与最佳匹配的算法 11.6 fleury算法 11.7 中国邮路问题及其算法 11.8 深度优先搜索法--dfs算法 11.9 项目网络与关键路径法 11.10 网络最大流算法 11.11 状态转移法 11.12 好算法、坏算法和np类问题 11.13 npc类问题 11.14 货郎问题的近似解 习 题... 参考文献

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值