• 容斥原理在程序设计中的应用


    1.定义

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

    举个栗子,如果被计数的事物有A、B、C三类,那么,A类和B类和C类元素个数总和= A类元素个数+ B类元素个数+C类元素个数—既是A类又是B类的元素个数—既是A类又是C类的元素个数—既是B类又是C类的元素个数+既是A类又是B类而且是C类的元素个数。

    (A∪B∪C = A+B+C - A∩B - B∩C - C∩A + A∩B∩C)

    推导公式:

    其实对于(2),是在(1)的基础上运用了Demorgen定理。


    2.严格证明

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

    注:以上证明摘自曹汝成.组合数学:华南理工大学出版社,2000年


    3.应用

      容斥原理的目的:找到不重复的总区间

      | Ai ∪ Aj ... ∪ An | = ∑ | A| - ∑ | Ai ∩ Aj | + ∑ | Ai ∩ Aj ∩ Ak | - ... + (-1)n × ∑ | Ai ∩ ... ∩ An |

    1.How many integers can you find HDU - 1796

    题目大意:给定一个n和一个有m个元素的序列,求小于n并且能被这m个元素中整除的整数有多少个。

    分析:在区间[1,n)中,找到能被每个元素整除的元素个数,用容斥原理找到不重复元素的个数。(其实就是题2省去分解质因数的简化版)

    实现:求最大公约数,最小公倍数:

    ll gcd(ll a,ll b){
        if(!b)return a;
        return gcd(b,a % b);
    }
    ll lcm(ll a,ll b){
        ll p = a * b / gcd(a,b);
        return p;
    }

      DFS容斥原理:按照图示层次递归

    ll DFS(ll i,ll w,int k,ll m){
        ll p;
        for(;i<=top;i++){
            p = lcm(a[i],w);
            ans += k * (m/p);
            DFS(i+1,p,-k,m);
        }
        return ans;
    }

    2.Co-prime HDU - 4135

    题目大意:给定一个区间[A,B],找出在这个区间内与给定的n互质的整数的个数。

    分析:直接求与n互质的数不好找,我们选择先找不与n互质的数,将n分解质因数,再通过容斥原理找到给定区间中所有质因数的倍数。

    举个栗子,n为30,那么质因数为2、3、5, 2在[1,30]中的倍数与3在[1,30]中的倍数难免有重复,这也不难解释我们为什么会用容斥原理了。

    设每个质因数在区间中的倍数分别有A1 、A2、A3 ... An个,那么∑ | Ai ∩ Aj |就是i、j的最小公倍数在区间中的倍数个数。

    | S | = ∑ | A| - ∑ | Ai ∩ Aj | + ∑ | Ai ∩ Aj ∩ Ak | - ... + (-1)n × ∑ | Ai ∩ ... ∩ An |

    质因数分解

    void div(ll n){
        for(int i=2; i*i<=n;i++){
            if(n % i == 0){
                a[++top] = i;
                while(n % i == 0)n /= i;
            }
            if(n == 1)break;
        }    
        if(n > 1)a[++top] = n;
    }

     

    3.Card Collector HDU - 4336

    题目大意:N个物品,每次得到第i个物品的概率为pi,而且有可能什么也得不到,问期望多少次能收集到全部N个物品。

    分析:这里牵涉到概率论上的一些基本概念——期望。举个栗子,两个元素的期望值,实际就是A1∩A2的期望值,而这里每个事件显然是互相独立的,那么A1∩A2显然等于A1 + A2,因此这两个元素的期望值应该是1/(A1 + A2),

    我们可以看到,A1的期望显然和A1∩A2的期望是有重叠部分,那么这里就解释了为什么会用到容斥原理了。

    void DFS(int i,double w,int k){
        double p;
        for(;i<=n;i++){
            p = a[i] + w;
            ans += k * (1 / p);
            DFS(i+1,p,-k);
        }
    }

    4.Visible Trees HDU - 2841 、仪仗队  Luogu P2158 [SDOI2008]

    HDU——2841 一个n*m的方格纸,格点上有树,求从(0,0)处看,能看到几棵树。

    P2158 [SDOI2008] 作为体育委员,C君负责这次运动会仪仗队的训练。仪仗队是由学生组成的N * N的方阵,为了保证队伍在行进中整齐划一,C君会跟在仪仗队的左后方,根据其视线所及的学生人数来判断队伍是否整齐(如下图) 在,C君希望你告诉他队伍整齐时能看到的学生人数。

    分析:如何判断一个人是否被遮挡?它与观测点连线的斜率,和离观测点更近的一个人与观测点连线的斜率相等。

    举个栗子,观察上图,后面的人显然就被前面的人遮挡了。若观测点为(0,0),前一个数为(2,1),后一个数为(4,2)。那么后一个人就被前一个人遮挡了。

    如果一个一个点求斜率再比较,数据范围 m,n<=100,000,O(mn)算法是超时算法。

    抽象一点说,若一个人的坐标有不为1的公因数,即坐标为(x*q,y*q),q ∈ N* ,那么势必会被坐标为(x,y)的人遮挡。

    所以就是求解x∈[1,m],y∈[1,n]中互质的数的个数。以列来枚举,列数为i,i∈[1,m],则就是求[1,n]中与i互质的数。

    如何求该区间内与i互质的数呢?还是找到i的所有质因数,利用容斥定理求解。

    其实第二题还有更巧妙的解法。在找到i的质因数后,利用欧拉函数求解。

    由于第二题图是n×n的正方形,所以将图分为对称的两半,则是求[1,i]中与i互质的数的个数。我们知道欧拉函数的定义φ(x)为在小于等于x的数中与x互质的数的个数,那么通过欧拉函数计算公式,就可以更快,更直接地求解了。

     

    附蒟蒻代码一篇:

    // P2158
    #include<cstdio> #include<cstring> #include<iostream> #include<cmath> using namespace std; // 欧拉函数模板 int main(){ int k,n,t = 0; long long ans = 0; cin>>k;// k*k 的正方形矩阵 for(int j = 2; j < k; j++)// 高从2到k-1,为什么是k-1不是k? //因为最后一列是k个节点,但我们判断是否互质是根据实际长度,即节点之间的距离判断的 { n = j;//一个小细节,由于找质因数时n被除值会改变,所以不直接用 j,以免影响外循环 long long int anss = 1; for(int i=2; i*i<=j; i++){ if(n % i == 0){ t = 0; while(n % i == 0)n /= i,t++; anss *= (long long int)(i-1) * (int)pow(i,t-1); } if(n <= 1)break; } if(n > 1)anss *= (n-1) ; ans += anss; } if(k == 1)printf("0");// 1*1的矩阵要特判,此时只能看到0个人 else printf("%lld",ans*2 + 3);// +3是考虑到直线x = 0,y = 0,y = x都只能看到一个点 return 0; }

     

    5.被破坏的玉米地

    输入一个整数n(0≤n≤200),表示圆圈的个数。以下n行每行有两个整数x和y,由空格分开,代表圆心坐标。

    当两个圆的圆心坐标为(0,0)和(1,0)时 图3.1两个圆所覆盖的面积为5.0548。编写程序需要输出统计的总面积,四舍五入到小数点后四位。

    分析:

    题意要求求出覆盖的总面积,考虑到圆之间可能有相交的情况,我们不能仅仅求所有圆的面积之和。圆之间可能有2个圆相交的部分、3个圆相交的部分、4个圆相交的部分......

    但如何求圆之间相交的面积呢?我们分类讨论来看一看。

    一、2个圆相交

      我们称这种为α型交

        称这种为β型交

     

    二、3个圆相交

      

    三、4个圆相交

        

    四、幸运的是,由于四个圆大小相同,没有五个圆互不重叠并且有共同相交面积的情况。

     

    接下来就是判断圆之间的相交类型,并通过容斥原理求覆盖总面积。

    由于代码太过繁杂,蒟蒻摘抄了大佬代码:

    #include <stdlib.h>
    #include <stdio.h>
    #include <iostream>
    #include <math.h>
    using namespace std; 
    #define MAX 80
    const double pi=3.14159265358979324;
    /**原点坐标**/
    struct coordinate
    {
        int x;
        int y;
    };
    /**计算任意两个坐标之间的距离”,这里的距离定义为横坐标之差与纵坐标之差的和**/
    int f1(coordinate k1,coordinate k2)
    {
        int l;
        if(abs(k1.x-k2.x)==0&&abs(k1.y-k2.y)==2){
            return 0;
        }
        if(abs(k1.x-k2.x)==2&&abs(k1.y-k2.y)==0){
            return 0;
        }
        l=abs(k1.x-k2.x)+abs(k1.y-k2.y);
        if (l>2)    return 0;
        else return l;
    }
    /**快速排序算法的比较函数**/
    int compare( const void *a, const void *b )
    {
        coordinate *arg1=(coordinate*)a;
        coordinate *arg2=(coordinate*)b;
        if (arg1->x<arg2->x)
            return -1;
        else if(arg1->x>arg2->x)
            return 1;
        else if (arg1->y<arg2->y)
            return -1;
        else if(arg1->y>arg2->y)
            return 1;
        else return 0;        
    };
    
    int main()
    {
        int i,n;
        double area;
        coordinate *a;
     
        double duration;
        
        //输入圆心的个数
        cin>>n;
        
        a=(coordinate*)malloc(sizeof(coordinate)*n);
        qsort(a,n,sizeof(coordinate),compare);
    
        int *List;
        List=(int*)malloc(sizeof(int)*n*n);
        
        int j,k,l,count1=0,count2=0,count3=0,count4=0;
        area=n*pi;
        
        //四种情况的相交面积 
        double s1=(2.0/3.0)*pi-sqrt(3.0)/2.0;
        double s2=pi/2.0-1.0;
        double s3=5.0/12.0*pi-sqrt(3.0)/2.0;
        double s4=pi/3.0-1+0.25/(sin(75*pi/180)*sin(75*pi/180));
        
        //分析两个圆相交的情况 
        for (i=0;i<n-1;i++)
            for (j=i+1;j<n;j++)
            {
                *(List+n*i+j)=f1(a[i],a[j]);
                *(List+n*j+i)=*(List+n*i+j);
                if (*(List+n*i+j)==1)
                    count1++;
                else if ((*(List+n*i+j)==2)&&(abs(a[i].x-a[j].x)==1))
                    count2++;
            }
            
        //分析三个圆或者四个圆相交的情况 
        for (i=0;i<n-2;i++)
            for (j=i+1;j<n-1;j++)
                for (k=j+1;k<n;k++)
                {
                    bool check=true;
                    int ans=*(List+n*i+j)+*(List+n*j+k)+*(List+n*i+k);
                    if(*(List+n*i+j)==0||*(List+n*j+k)==0||*(List+n*i+k)!=0)
                    if(ans==4&&check)
                    {
                        count3++;
                    }
                }
     
        for (i=0;i<n;i++)
            for (j=0;j<n;j++)
                for (k=0;k<n;k++)
                    for(l=0;l<n;l++){
                            if ((*(List+n*i+j)==1)&&(*(List+n*j+k)==1)&&(*(List+n*k+l)==1)&&(*(List+n*i+l)==1)&&(i!=j)&&(i!=k)&&(i!=l)&&(j!=k)&&(j!=l)&&(k!=l))
                                count4++;
                    }        
        
        area=area-s1*count1-s2*count2+s3*count3-s4*count4/8;
     
        free(a);
        free(List);
    }

    摘自:https://blog.csdn.net/larry1648637120/article/details/86529012

    推荐一道同样是求交集面积的题:P4515 [COCI2009-2010#6] XOR

    坐标系下有若干个等腰直角三角形,且每个等腰直角三角形的直角顶点都在左下方,两腰与坐标轴平行。被奇数个三角形覆盖的面积部分为灰色,被偶数个三角形覆盖的面积部分为白色,如下图所示。

    已知 N个等腰直角三角形的顶点坐标及腰长,求灰色部分面积。

    6.素数四元组的个数问题  

    SP4191 MSKYCODE - Sky Code

           给出n个数,从其中选出4个数,使它们的最大公约数为1,问总共有多少中取法。

            我们解决它的逆问题:求最大公约数d>1的四元组的个数。

            运用容斥原理,将求得的对于每个d的四元组个数的结果进行加减。

             

             其中deg(d)代表d的质因子个数,f(d)代表四个数都能被d整除的四元组的个数。

             求解f(d)时,只需要利用组合方法,求从所有满足被d整除的ai中选4个的方法数。

             然后利用容斥原理,统计出所有能被一个素数整除的四元组个数,然后减掉所有能被两个素数整除的四元组个数,再加上被三个素数整除的四元组个数…

    7.和睦数三元组的个数问题

            给出一个整数 。选出a, b, c (其中2<=a<b<c<=n),组成和睦三元组,即:

    满足 ,  , 

    或者满足

    首先,我们考虑它的逆问题:也就是不和睦三元组的个数。

    然后,我们可以发现,在每个不和睦三元组的三个元素中,我们都能找到正好两个元素满足:它与一个元素互素,并且与另一个元素不互素。

    所以,我们只需枚举2到n的所有数,将每个数的与其互素的数的个数和与其不互素的数的个数相乘,最后求和并除以2,就是要求的逆问题的答案。

    现在我们要考虑这个问题,如何求与2到n这些数互素(不互素)的数的个数。虽然求解与一个数互素数的个数的解法在前面已经提到过了,但在此并不合适,因为现在要求2到n所有数的结果,分别求解显然效率太低。

    所以,我们需要一个更快的算法,可以一次算出2到n所有数的结果。

    在这里,我们可以使用改进的埃拉托色尼筛法。

    ·首先,对于2到n的所有数,我们要知道构成它的素数中是否有次数大于1的,为了应用容斥原理,我们还有知道它们由多少种不同的素数构成。

    对于这个问题,我们定义数组deg[i]:表示i由多少种不同素数构成,以及good[i]:取值true或false,表示i包含素数的次数小于等于1是否成立。

    再利用埃拉托色尼筛法,在遍历到某个素数i时,枚举它在2到n范围内的所有倍数,更新这些倍数的deg[]值,如果有倍数包含了多个i,那么就把这个倍数的good[]值赋为false。

    ·然后,利用容斥原理,求出2到n每个数的cnt[i]:在2到n中不与i互素的数的个数。

    回想容斥原理的公式,它所求的集合是不会包含重复元素的。也就是如果这个集合包含的某个素数多于一次,它们不应再被考虑。

    所以只有当一个数i满足good[i]=true时,它才会被用于容斥原理。枚举i的所有倍数i*j,那么对于i*j,就有N/i个与i*j同样包含i(素数集合)的数。将这些结果进行加减,符号由deg[i](素数集合的大小)决定。如果deg[i]为奇数,那么我们要用加号,否则用减号。

  • 相关阅读:
    14个你可能不知道的JavaScript调试技巧
    数据库设计四步骤
    mac 卸载 jdk
    node版本管理
    mysql order by limit 问题
    计算机一些基本概念的认识
    SQL设置主外键关联时报错
    阻止表单autocomplete
    常见字符编码
    编程语言分类
  • 原文地址:https://www.cnblogs.com/Cindy-Chan/p/11222564.html
Copyright © 2020-2023  润新知