• 容斥原理与二项式反演


    前(fei)言(hua)

    高二学长说CTS2019D1T1可以用二项式反演推容斥系数,但我当时是找规律找出来的。。。

    后来遇到一道类似的题才推了出来,然后知道了这就是二项式反演。。。

    然后发现网上的博客都只写了证明啊。。。

    下面就讲一下二项式反演是怎么来的

    二项式定理(讲得这么高大上其实就是一句废话)

    [{(a+b)}^n=sum_{k=0}^n C_n^ka^kb^{n-k} ]

    遇到(sum_{i=0}^k(-1)^iC_k^i)的时候可以补上一个(1^{k-i})然后倒着用二项式定理,最后可以化简为(0^k)

    容斥原理(小学数学复习)

    小学经常见到的那张图

    回顾一下小学学过的容斥原理:

    其中(S)为三种属性,一个物品可以表示成一个属性的集合,(ABCDEFG)为一些属性集合相同的物品组成的集合。

    比如:一个学生参加了活动(S1)(S2),他的属性集合可以被表示为({S1,S2}),所以他属于集合(D)

    一个经典小学数学问题

    小学数学中的一个经典问题是:知道所有至少具有属性S1的物品数和((S2,S3)类似)、至少具有(S1)(S2)属性的物品数((S1)(S3)(S2)(S3)类似)和同时具有(S1,S2,S3)属性的物品数,求所有物品的数量。

    我们定义(X1,X2,X3)分别为至少具有(S1,S2,S3)属性的物品的集合,那么(X1=A+D+E+G,X1cap X2=D+G),以此类推。

    问题可以表示为:知道(|X1|,|X2|,|X3|,|X1cap X2|,|X1cap X3|,|X2cap X3|,|X1cap X2cap X3|),求(|X1cup X2cup X3|)

    我们小学的时候就知道答案是至少一个属性的物品数量-至少两个属性的+至少三个的,即(|X1|+|X2|+|X3|-|X1cap X2|-|X1cap X3|-|X2cap X3|+|X1cap X2cap X3|)。因为(|X1|+|X2|+|X3|)把两个属性的物品多加了一次,所以要减去。然后我们发现三个属性的物品加了三次又被减了一次,所以我们要把它加回去。

    如果考虑的属性更多会怎样

    考虑属性更多的情况,我们可以做出假设:

    [|X_1cup X_2 cup ... cup X_n|=sum_{i=1}^n |X_i|-sum_{i<j} |X_icap X_j|+sum_{i<j<k} |X_icap X_jcap X_k|- ... +(-1)^{n-1}||X_1cap X_2 cap ... cap X_n| ]

    证明:

    考虑一个在(k)个集合中的元素((k>0))放在上面的例子中就是有(k)种属性的物品)被计算了多少次,假如我们把(i)个集合求交,这样的交显然是(C_k^i)个,所以我们可以枚举(i)

    [egin{aligned} ans&=sum_{i=1}^k(-1)^{i-1}C_k^i\ &=1-sum_{i=0}^k(-1)^iC_k^i\ &=1-(1+(-1))^k\ &=1 end{aligned} ]

    这就是容斥原理。

    子集反演(听说是叫这个但是我喜欢叫它容斥)

    另一个经典小学数学问题

    条件还是一样:对于每个属性集合,我们知道至少包含它的物品个数。我们现在要求只有某种属性的物品个数。

    (S1)为例,答案就是((A+D+E+G)-(D+G)-(E+G)+(G)),可以感性理解为至少包含一个的-至少包含两个的+至少包含三个的。

    (S1)改成({S1,S2})上述方法也适用。

    我们用(f(X))表示(X)的超集的权值和(本例中权值就是属性集合为这个集合的物品数),(g(X))表示集合(X)的权值。

    即$$f(S)=sum_{Tsupseteq S} g(T)$$

    在本例中,(g(A)=f(A)-f(D)-f(E)+f(G))

    我们可以发现:$$g(S)=sum_{Tsupseteq S} (-1)^{|T|-|S|}f(T)$$

    其意义很好理解,证明就代进去稍微化简一下就行了。

    [egin{aligned} g(S)&=sum_{Tsupseteq S} (-1)^{|T|-|S|}sum_{Usupseteq T} g(U)\ &=sum_{Usupseteq S} g(U) sum_{T'subseteq U-S} (-1)^{|T'|}\ &=sum_{Usupseteq S} g(U) sum_{k=0}^{|U-S|} (-1)^kC_{|U-S|}^k\ &=sum_{Usupseteq S} g(U) [U-S=varnothing]\ &=g(S) end{aligned} ]

    还有一个经典小学数学问题

    (f(X))表示(X)的子集的权值和,(g(X))表示集合(X)的权值。

    (g(G)=f(G)-f(D)-f(E)-f(F)+f(A)+f(B)+f(C))

    然后发现:

    [egin{aligned} f(S)&=sum_{Tsubseteq S} g(T)\ g(S)&=sum_{Tsubseteq S} (-1)^{|S|-|T|}f(T) end{aligned} ]

    比上一个还简单,留给读者自己思考。

    二项式反演

    一种形式

    我们知道$$f(S)=sum_{Tsubseteq S} g(T)Leftrightarrow g(S)=sum_{Tsubseteq S} (-1)^{|S|-|T|}f(T)$$

    如果集合的权值只和大小有关,用(f(|S|))代替(f(S))(g(|S|))代替(g(S)):$$f(n)=sum_{i=0}^n C_n^ig(i)Leftrightarrow g(n)=sum_{i=0}^n (-1)^{n-i}C_n^if(i)$$

    (n)个元素的集合的大小为(i)的子集个数显然是(C_n^i)

    不信的话可以代进去验证一下。

    另一种形式

    [f(S)=sum_{Tsupseteq S} g(T)Leftrightarrow g(S)=sum_{Tsupseteq S} (-1)^{|T|-|S|}f(T) ]

    (f(n)=sum_{|S|=n} f(S),g(n)=sum_{|S|=n} g(S)),则

    [egin{aligned} f(n)&=sum_{|S|=n} f(S)\ &=sum_{|S|=n} sum_{Tsupseteq S}g(T)\ &=sum_{|T|geq n} g(T)sum_{|S|=n,Ssubseteq T} 1\ &=sum_{i=n}^msum_{|T|=i}g(T)C_i^n\ &=sum_{i=n}^mC_i^ng(i) end{aligned} ]

    同理$$g(n)=sum_{i=n}^m (-1)^{i-n}C_i^nf(i)$$

    所以$$f(n)=sum_{i=n}^mC_i^ng(i)Leftrightarrow g(n)=sum_{i=n}^m (-1)^{i-n}C_i^nf(i)$$

    按理来说应该还有两种形式,不知道有没有用。

    别人博客里写的基本形式

    [f(n)=sum_{i=0}^n (-1)^iC_n^ig(i)Leftrightarrow g(n)=sum_{i=0}^n (-1)^iC_n^if(i) ]

    (-1)乘到(g)里面就是第一种形式了(不知道这是拿来干嘛的)。

    bzoj3622

    题目大意

    给两个长度为(n)的序列(A,B),把其中的数两两配对,求(A)中的数大于(B)中的数恰好有(k)对的方案数对(10^9+9)取模的结果,(nleq 2000)

    题解

    首先可以(dp)(k)对合法剩下乱放的方案数,然后套一下上面所说的第二种形式就行了。

    (实在不懂的话就是把一个配对方案表示成一个合法配对的集合,一个集合的权值就是能够表示成它的方案数。(k)个合法配对剩下乱配对的方案数就是这(k)个元素组成的集合的超集的权值和,(dp)值的意义就是所有大小为(k)的集合的这个值之和;我们最后要求的答案就是所有大小恰好为(k)的集合的权值和)

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    const int mxn=4096,md=1000000009;
    int n,k,a[mxn],b[mxn],f[mxn],c[mxn][mxn];
    int main()
    {
        scanf("%d%d",&n,&k);
        if ((n+k)&1) return puts("0"),0;
        k=(n+k)>>1;
        for (int i=1;i<=n;++i) scanf("%d",&a[i]);
        sort(a+1,a+n+1);
        for (int i=1;i<=n;++i) scanf("%d",&b[i]);
        sort(b+1,b+n+1);
        f[0]=1;
        for (int i=1,cur=0;i<=n;++i){
            for (;cur<n&&b[cur+1]<a[i];++cur);
            for (int j=i;j;--j)
                f[j]=(f[j]+f[j-1]*(cur-j+1ll))%md;
        }
        for (int i=n-1,num=1;i>=0;--i)
            num=1ll*num*(n-i)%md,f[i]=1ll*f[i]*num%md;
        for (int i=0;i<=n;++i) c[i][0]=1;
        for (int i=1;i<=n;++i)
            for (int j=1;j<=i;++j)
                c[i][j]=(c[i-1][j-1]+c[i-1][j])%md;
        int ans=0;
        for (int i=k,flg=1;i<=n;++i,flg=-flg)
            ans=(ans+1ll*flg*c[i][k]*f[i])%md;
        printf("%d
    ",(ans+md)%md);
        return 0;
    }
    
  • 相关阅读:
    Django框架之ORM常用字段和参数、单表操作
    Django框架之模板层
    Django框架之视图层
    Django框架之路由层,本地虚拟环境搭建
    Django框架
    Django框架
    在学习Django框架之前所需要了解的知识点
    JQuery
    Android开发(八)-界面编程
    Android开发(七)-签名APK
  • 原文地址:https://www.cnblogs.com/zzqtxdy/p/10958513.html
Copyright © 2020-2023  润新知