zoukankan      html  css  js  c++  java
  • 组合数学

    组合数学

    研究一个集合内满足一定规则的排列问题

    (1)存在问题,判断排列是否存在

    (2)计数问题,计算出有多少个排列

    (3)优化问题

    4.1  抽屉原理(鸽巢原理)---ramsey原理特列

    把n+1个物体放进n个盒子里,至少有1个盒子包含2个或者更多的物体

    例如:吃糖果(隔板法)

    4.2 杨辉三角和二项式系数

    Cnk---(二项式定理)-----(1+x)^n------杨辉三角第n行

    4.3 容斥原理

    4.4 Fibonacci数列

    应用:楼梯问题、矩形覆盖问题

    n<=10^6 用公式即可

    若数更大了,需要用到矩阵快速幂,把递推关系转化为矩阵

    例:求Fibonacci第n项

    #include<iostream>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<stack>
    #include<cstdio>
    #include<queue>
    #include<map>
    #include<vector>
    #include<set>
    using namespace std;
    const int maxn=1010;
    const int INF=0x3fffffff;
    typedef long long LL;
    typedef unsigned long long ull;
    //这个是模板
    //斐波那契数列取模
    /*
    (1)
    
    1 0    *     1 1     =   1 1
                 1 0
    (2)
    1 1    *     1 1     =   2 1
                 1 0
    (3)
    2 1    *     1 1     =   3 2
                 1 0
    所以第n项就是1 0 *       (1,1)^n
                            (1,0)
    
    用快速幂优化就是矩阵快速幂了
    */ 
    LL n,mod;
    LL a[3][3],b[3][3],ans[3][3],c[3][3];
    void add(LL &x,LL y){
    	x=x+y;
    	x-=(x>=mod)?mod:0;
    	return;
    }
    //		memmove用于拷贝字节,如果目标区域和源区域有重叠的话,memmove能够保证源串在被覆盖之前将重叠区域的字节拷贝到目标区域中,但复制后源内容会被更改。
    //但是当目标区域与源区域没有重叠则和memcpy函数功能相同。
    int main(){
    	scanf("%lld %lld",&n,&mod);
    	n-=1;
    	a[1][1]=a[1][2]=a[2][1]=1;a[2][2]=0;
    	ans[1][1]=ans[2][2]=1;ans[2][1]=ans[1][2]=0;
    	while(n){
    		if(n&1){  //快速幂 
    			memset(c,0,sizeof(c));
    			for(int i=1;i<=2;i++){
    				for(int j=1;j<=2;j++){
    					for(int k=1;k<=2;k++) add(c[i][j],ans[i][k]*a[k][j]%mod);
    				}
    			}
    			memmove(ans,c,sizeof(ans));  //这个函数是实现字节的拷贝
    		}
    		memset(c,0,sizeof(c));
     		//对a进行倍乘
    		for(int i=1;i<=2;i++)
    		for(int j=1;j<=2;j++){
    			for(int k=1;k<=2;k++) add(c[i][j],a[i][k]*a[k][j]%mod);
    		} 
    		memmove(a,c,sizeof(a));
    		n>>=1;
    	}
    	b[1][1]=1;b[1][2]=0;
    	memset(c,0,sizeof(c));
    	for(int i=1;i<=1;i++){
    		for(int j=1;j<=2;j++){
    			for(int k=1;k<=2;k++) add(c[i][j],ans[i][k]*b[k][j]);
    		}
    	}
    	memmove(b,c,sizeof(b));
    	printf("%lld
    ",b[1][1]);
    return 0;
    }
    

      

    4.5  母函数

    用代数方法解决组合计数问题

    整数划分问题(把一个整数划分为多个整数的和,这些数大于1,小于等于n),详解:https://www.cnblogs.com/radiumlrb/p/5797168.html

    解决方法:递归、DP、母函数(how:把组合问题的加法与幂级数的幂乘对应起来)

    母函数:

    (x^(0*1)+x^(1*1)+x(2*1)+....)*(x^(0*2)+x^(1*2)+x^(2*2)+...)*(x^(0*3)+x^(1*3)+x^(2*3)+....)..

    =(1+x+x^2+...)*(1+x^2+x^4+...)*(1+x^3+x^6)....

    分别表示不用数字1,用1次,用2次...

    普通型母函数---求组合方案数

    //母函数求整数划分
    int c1[maxn],c2[maxn];
    void part(){
    	for(int i=0;i<=maxn;i++){ //初始化,第一部分(1+x+x^2+...)的系数,都是1 
    		c1[i]=1;c2[i]=0;
    	}
    	for(int k=2;k<=maxn;k++) { //从第二部分(1+x^2+x^4+..)开始展开
    		for(int i=0;i<=maxn;i++){
    			//k=2是,i循环第一部分(1+x+x^2+...),j循环第二部分 (1+x^2+x^4+..)
    			for(int j=0;j+i<=maxn;j+=k){
    				c2[i+j]+=c1[i];
    			}
    			for(int i=0;i<=maxn;i++){
    				c1[i]=c2[i];
    				c2[i]=0;
    			}
    		}
    	}
     //c1[n]用来记录每次展开后第x^n项的系数,结束后,c1[n]就是整数n的划分数 
    } 
    

    指数型母函数----求排序数

    例如:hdu 1521 排序组合

    指数型母函数

    k个物品的排列和k个物品的组合相差k!倍

    指数阶一般求解的问题:已知有n种颜色的球,第1种X1个,第2种X2个,第3种X3个。。。求从中取m个的方案数(组合数)。

    公式中的ak/k!就是所求的组合数,ak为排列数

    只要把求系数的时候每个都相应的 除以i的阶乘即可

    #include<iostream>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<stack>
    #include<cstdio>
    #include<queue>
    #include<map>
    #include<vector>
    #include<set>
    using namespace std;
    const int maxn=1010;
    const int INF=0x3fffffff;
    typedef long long LL;
    typedef unsigned long long ull;
    //指数型母函数
    /*
    指数型母函数
    指数阶一般求解的问题:已知有n种颜色的球,第1种X1个,第2种X2个,第3种X3个。。。求从中取m个的方案数(组合数)。
    公式中的ak/k!就是所求的组合数,ak为排列数。
    只要把求系数的时候每个都相应的 除以i的阶乘即可
    另一种方法:https://blog.csdn.net/a601025382s/article/details/10274259 
    */ 
    int a[21],c[11];
    int n,m;
    int main(){
        c[0]=1;
        for(int i=1;i<=10;i++) c[i]=c[i-1]*i;
        double c1[21],c2[21];
        while(~scanf("%d %d",&n,&m)){
            memset(c1,0,sizeof(c1));
            memset(c2,0,sizeof(c2));
            for(int i=1;i<=n;i++) scanf("%d",&a[i]); //个数
            c1[0]=1.0;
            for(int i=1;i<=n;i++){
                for(int j=0;j<=m;j++)
                    for(int k=0;k+j<=m&&k<=a[i];k++)
                        c2[j+k]+=c1[j]/c[k];
                for(int j=0;j<=m;j++){
                    c1[j]=c2[j];
                    c2[j]=0;
                }
            }
            printf("%.0f
    ",c1[m]*c[m]); 
        }
    return 0;
    }
    

      

    另一种方法:https://blog.csdn.net/a601025382s/article/details/10274259

    有n种物品,每种取ai种(∑ai==m),则方案数有
    ans=m!/(a1!*a2!*...an!)=m!/(a1!*(m-a1)!)*(m-a1)!/(a2!*(m-a1-a2)!)...=c(m,a1)*c(m-a1,a2)*...
    含义就是第一种物品有a1个,由于相同,所以就是在m个位置上选a1个,共c(m,a1)种放法,剩下m-a1个位置,
    再第二种物品有a2个,再在m-a1个位置选a2个放,有c(m-a1,a2)。。。以此类推就是方案数了。。
    然后用变形的背包或则说dp..来解,
    f[i]表示已经选好了i件的排列数。。应该这么叫吧,如果没有排列这个就是方案数了。

    这里的好处就是(a+b)*c=a*c+b*c;原本应该独立处理每种{ai}序列,最后加上排列数,但这种耗时太大,不能用。
    所以就要压缩时间,对于队列两个序列{ai},{bi},如果a1+a2=b1+b2=p,a3=b3=q,;则c(m,a1)*c(m-a1,a2)*c(m-a1-a2,a3)+
    c(m,b1)*c(m-b1,b2)*c(m-b1-b2,b3)=(c(m,a1)*c(m-a1,a2)+c(m,b1)*c(m-b1,b2))*c(m-p,q);
    这就解释了,为什么上面不用求出每个方案,然后求排列了。。

    #include <cstdio>
    #include <cmath>
    #include <cstring>
    #include <vector>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    int c[11][11];//组合数
    int f[11],a[11];
    void init()
    {
        int i,j;
        memset(c,0,sizeof(c));
        c[1][0]=c[1][1]=1;
        for(i=2;i<=10;i++)
        {
            c[i][0]=c[i][i]=1;
            for(j=1;j<i;j++)
                c[i][j]=c[i-1][j]+c[i-1][j-1];
        }
    }
    int main()
    {
        init();
        int n,m;
        while(cin>>n>>m)
        {
            int i,j,k;
            for(i=0;i<n;i++)
                cin>>a[i];
            memset(f,0,sizeof(f));
            f[0]=1;
            for(i=0;i<n;i++)
            {
                for(j=m;j>=0;j--)//枚举已经选择了j件物品
                {
                    for(k=1;k<=min(a[i],m-j);k++)//k不能为0不然就会加上本身
                        f[k+j]+=c[m-j][k]*f[j];   //每次都只处理用f[j]处理f[j+k],j+k>j,使得不会因为前面处理影响后面的处理
                }
            }
            cout<<f[m]<<endl;
        }
        return 0;
    }
    

      

    4.6 特殊计数

    (1) Catalan数

    http://www.cppblog.com/MiYu/archive/2010/08/07/122573.html

    Cn=1/(n+1)*C(2n,n),是许多组合计数问题的数学模型,是一个很常见的数列

    模型1:Cn=1/(n+1)C(2n,n)=C(2n,n)-C(2n,n+1)=C(2n,n)-C(2n,n-1)

    C(2n,n+1)与C(2n,n-1)等价

    推导:把n个1和n个0排成一行,使这一行前k个数中1的数量总是大于或者等于0的数量(0>=1等价),排列有多少个?一共有Cn个,即Catalan数

    模型2:递推

    Cn=C0Cn-1+C1Cn-2+...+Cn-2C1+Cn-1C0,  C0=1

    应用场景:

    (1)棋盘问题

    例如:hdu 2067 

    小兔的叔叔从外面旅游回来给她带来了一个礼物,小兔高兴地跑回自己的房间,拆开一看是一个棋盘,小兔有所失望。不过没过几天发现了棋盘的好玩之处。从起点(0,0)走到终点(n,n)的最短路径数是C(2n,n),现在小兔又想如果不穿越对角线(但可接触对角线上的格点),这样的路径数有多少?小兔想了很长时间都没想出来,现在想请你帮助小兔解决这个问题,对于你来说应该不难吧!

    意思:从左下角走到右上角,一直在对角线右下方走,不穿过主对角线,有多少种走法? -----模型1

    对方向编号,向右是0,向上是1,在前k步中,0数量大于1数量,

    两种写法都可以(第二种没看懂)

    #include<iostream>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<stack>
    #include<cstdio>
    #include<queue>
    #include<map>
    #include<vector>
    #include<set>
    using namespace std;
    const int maxn=1010;
    const int INF=0x3fffffff;
    typedef long long LL;
    typedef unsigned long long ull;
    /*按照正常思路来求解:https://www.cnblogs.com/liudehao/p/4113876.html
     一定要看明白那个图! 
     观察上图你就可以发现,其实这是一张关于对角线对称的图。所有我们只要求一个方向的值,然后乘以2即可。
    我们就拿下三角来考虑。不难发现,所有在0列上的格子,路径数都是 1 (只能从上面过来)。
    而其他格子则都是由上、左两个方向过来,即:f(i, j) = f(i - 1, j) + f(i, j - 1);
    另外f(i, i) = f(i, j - 1)  或者 f(i, i) = f( i-1, j ) ;
    
    LL f[40][40];
    int cases=0;
    int main(){
        int n;
        while(scanf("%d",&n)!=EOF){
            ++cases;
            if(n==-1) break;
            for(int i=1;i<=n;i++) f[0][i]=1;
            for(int i=1;i<n;i++){
                for(int j=i;j<=n;j++){
                    if(i==j) f[i][j]=f[i-1][j];
                    else f[i][j]=f[i-1][j]+f[i][j-1];
                }
            }
            printf("%d %d %lld
    ",cases,n,2*f[n-1][n]);
        }
    return 0;
    }
    
     */ 
    
    /*卡特兰数求法
     令h(1)=1,h(0)=1,catalan数满足递归式:
      h(n)= h(0)*h(n-1)+h(1)*h(n-2) +  + h(n-1)h(0) (其中n>=2)
      另类递归式:
      h(n)=((4*n-2)/(n+1))*h(n-1);
      该递推关系的解为:
      h(n)=C(2n,n)/(n+1) (n=1,2,3,…)
    */
    int main(){
        LL a[40][40];
        a[0][0]=0;a[0][1]=1;a[1][1]=2;
        for(int i=2;i<37;i++){
            a[i][0]=1;
            for(int j=1;j<i-1;j++) a[i][j]=a[i][j-1]+a[i-1][j];
            a[i][i-1]=a[i][i-2]+a[i-1][i-1]/2;
            a[i][i]=2*a[i][i-2]+a[i-1][i-1];
        }
        int n,cases=0;
        while(scanf("%d",&n)!=EOF){
            ++cases;
            if(n==-1) break;
            printf("%d %d %lld
    ",cases,n,a[n][n]);
        }
        return 0;
    } 
    

      

    (2)括号问题

    n个(和n个)组成字符串,有多少合法的组合,合法的组合是:任意前k个括号组合,左括号的数量大于等于有括号的数量

    --->模型1

    hdu 5184

    (3)出栈序列问题

    给定一个入栈序列,求出多少字可能的出栈序列,合法的序列:对应每一个数字,在它后面的比它小的所有数字一定是按照递减序列排列的

    定义进栈为0,出栈为1,那么出栈序列要求进栈的操作数大于等于出栈的操作数,n个0和n个1

    hud 1023

    (4)二叉树问题

    n个节点构成的二叉树有多少种情况?

    模型2(左边多少个节点,右边多少个节点。。。。)

    (5)其他问题

    eg.买票找零、三角剖分(凸多边形内部划分为多个三角形有多少种方法)

    求法:

    卡特兰数求法
    令h(1)=1,h(0)=1,catalan数满足递归式:
    法1:h(n)= h(0)*h(n-1)+h(1)*h(n-2) + + h(n-1)h(0) (其中n>=2)
      
    法2:h(n)=((4*n-2)/(n+1))*h(n-1);
      
    法3:h(n)=C(2n,n)/(n+1) =2n!/(n+1)!n!   (n=1,2,3,…)

    法1对应的n较小,n<=100,对于(2)(3)的应用:n很大,不能直接输出,而是要做取模操作

    但是2、3都有大数除法,会损失精度,所以需要转化为逆元,然后取模

    (2)第二类Stirling数

    第一类Stirling数:仓库钥匙问题

    把n个仓库分配到k个圆里,不能有空的圆,有多少种分法:第一类Stirling数  (大一些)

    s(n,k)=s(n-1,k-1)+(n-1)*s(n-1,k)

    s(0,0)=1 

    s(k,0)=0

    第二类Stirling数:s(n,k):把n个不同的球分配到k个相同的盒子里,不能有空,有多少种分法?

    s(n,k)=k*s(n-1,k)+s(n-1,k-1)

    s(0,0)=1

    s(i,0)=0

  • 相关阅读:
    列表、元组、字典、集合类型及其内置方法
    Python数字类型及字符串类型的内置方法 ##
    Python之流程控制
    前端混合
    数据库
    oracle 11g 安装过程
    SQLAlchemy
    pipreqs快速生成python项目所需的依赖
    llinux基本操作
    linux简介
  • 原文地址:https://www.cnblogs.com/shirlybaby/p/13037409.html
Copyright © 2011-2022 走看看