组合数学

广义的组合数学就是离散数学,狭义的组合数学是图论、代数结构、数理逻辑等的总称。但这只是不同学者在叫法上的区别。总之,组合数学是一门研究离散对象的科学。
主要写写:
1、排列组合
2、错排公式
3、容斥原理
4、狄摩根定理
5、catalan数
6、母函数
7、Stirling数
8、鸽巢原理

一、排列组合:(有序排列、无序组合)
排列:

集合中有n个元素,从该集合中有顺序的无重复取r个,称之为排列。
P(n,r) = n(n-1)…(n-r+1) = n!/(n-r)!

有顺序的有重复
n^r

有顺序无重复的环形排列
P(n,r)/r

打出一个数列的全排列:(STL中的函数)

1
2
3
int a[N];
sort(a,a+N);
next_permutation(a,a+n);

递归函数打出全排列数:

1
2
3
4
5
6
7
8
9
10
11
12
13
void perm( int a[], int k, int n) {
     if (k>=n) {
         for ( int i=0; i<n; i++)
             printf ( "%d " , a[i]);
         printf ( "\n" );
     } else {
         for ( int i=k; i<n; i++) {
             swap(a[k], a[i]);
             perm(a,k+1,n);
             swap(a[k], a[i]);
         }
     }
}

组合:

C(n,r)=A(n,r)/r! = n! /((n-r)! r!)
C(n,r)=C(n,n-r)

帕斯卡递推
C(n,r)=C(n-1,r)+C(n-1,r-1)
C(n+r+1,r)=C(n+r,r)+C(n+r-1,r-1)+…+C(n+1,1)+C(n,0) ……证明根据帕斯卡递推可以证明
C(n,k)C(k,r)=C(n,r)C(n-r,k-r) …..由一般定理可以证明,化成阶乘的形式

重复组合:
C(n+r-1,r) …..取球问题,有放回的取球很明显能想通。相当于有n+r-1 个球,从中取r个球

一般简单的组合数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//a[n][m]表示C(n,m),最后打出a[n][m]即可
const int N=100; int a[N][N]; void init(){
     a[0][0]=0;
     for ( int i=1;i<N;i++){
         a[i][1]=i,a[i][i]=1,a[i][0]=0;
     }
     for ( int i=2;i<N;i++){
         for ( int j=2;j<i;j++){
             a[i][j]=a[i-1][j-1]+a[i-1][j];
         }
     }
}

快速求组合数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
long long cal( long long n, long long m) {
     long long i, a, b, p;
     if (n<m) {
         i=m;
         m=n;
         n=i;
     }
     p=1;
     a=n-m<m?n-m:m;
     b=n-m>m?n-m:m;
     for (i=1; i<=a; i++)
         p+=(p*b/i);
     return p;
}

大数取组合数求模

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//大数组合数取模:以C(n,m)%p.其中p必须为质数
long long p,m,n; long long Pow( long long a, long long b) {
     long long ans=1;
     while (b) {
         if (b&1) {
             b--;
             ans=(ans*a)%p;
         } else {
             b/=2;
             a=(a*a)%p;
         }
     }
     return ans;
} long long C( long long n, long long m) {
     if (n<m)
         return 0;
     long long ans=1;
     for ( int i=1; i<=m; i++) {
         ans=ans*(((n-m+i)%p)*Pow(i,p-2)%p)%p;
     }
     return ans;
} long long Lucas( long long n, long long m) {
     if (m==0)
         return 1;
     return Lucas(n/p,m/p)*C(n%p,m%p)%p;
}

二、错排公式

eg: 十本不同的书放在书架上。现重新摆放,使每本书都不在原来放的位置。有几种摆法????
考虑一个有n个元素的排列,若一个排列中所有的元素都不在自己原来的位置上,那么这样的排列就称为原排列的一个错排。 n个元素的错排数记为D(n)。

递推的推导错排公式

当n个编号元素放在n个编号位置,元素编号与位置编号各不对应的方法数用D(n)表示,那么D(n-1)就表示n-1个编号元素放在n-1个编号位置,各不对应的方法数,其它类推.

第一步,把第n个元素放在一个位置,比如位置k,一共有n-1种方法;
第二步,放编号为k的元素,这时有两种情况:
⑴把它放到位置n,那么,对于剩下的n-1个元素,由于第k个元素放到了位置n,剩下n-2个元素就有D(n-2)种方法;
⑵第k个元素不把它放到位置n,这时,对于这n-1个元素,有D(n-1)种方法;(相当于k是从第n的位置上去的元素,
这样就容易想通)

综上得到
D(n) = (n-1) [D(n-2) + D(n-1)]
特殊地,D(1) = 0, D(2) = 1.

三、容斥原理:

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

两个集合的容斥关系公式:A∪B = A+B – A∩B    (∩:重合的部分)

三个集合的容斥关系公式:A∪B∪C = A+B+C – A∩B – B∩C – C∩A +A∩B∩C

一般公式:

QQ图片20140827103403

容斥可以用dfs 搜索实现
用一个例子来看怎么实现容斥:

给定一个集合和一个数n,求小于n 的数中,满足可以被集合中的其中一个数整除的个。
题目:hdu 1796
http://acm.hdu.edu.cn/showproblem.php?pid=1796
关键代码:
代码很简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
const int N = 25; int a[N]; int n,m; int ans,cnt; int gcd( int a, int b){
     return b?gcd(b,a%b):a;
} int LCM( int a, int b){
     return a/gcd(a,b)*b;
}
  void dfs( int id, bool flag, int lcm){
     lcm=LCM(a[id],lcm);
     if (flag) ans+=n/lcm;
     else ans-=n/lcm;
     for ( int i=id+1;i<cnt;i++){
         dfs(i,!flag,lcm);
     }
}
  int main(){
     while (~ scanf ( "%d%d" ,&n,&m)){
         ans=0;
        cnt=0;
         int x;
         for ( int i=0;i<m;i++){
             scanf ( "%d" ,&x);
             if (x) a[cnt++]=x;
         }
         n--;
         for ( int i=0;i<cnt;i++){
             dfs(i, true ,a[i]);
         }
         printf ( "%d\n" ,ans);
     }
     return 0;
}

四、狄摩根定理
dm

五、 catalan数
满足:h(n)= h(0)*h(n-1)+h(1)*h(n-2) + … + h(n-1)h(0) (n>=2)

用多边行分成的三角形的种类可以证明,
h(0)=h(1)=1;
另类递推公式:
h(n)=h(n-1)*(4*n-2)/(n+1);
递推关系的解为:
h(n)=C(2n,n)/(n+1) (n=0,1,2,…)

递推关系的另类解为:
h(n)=c(2n,n)-c(2n,n+1)(n=0,1,2,…)

其前几项为 : 1, 2, 5, 14, 42, 132, 429, 1430, 4862, 16796, 58786, 208012, 742900, 2674440,9694845, 35357670, 129644790, 477638700, 1767263190, 6564120420, 24466267020, 91482563640, 343059613650, 1289904147324, 4861946401452

证明:h(n)=C(2n,n)/(n+1) (n=0,1,2,…)
用母函数证明

例题:
例题:(买票问题)
本次足球比赛的门票为50元,而站排买票的球迷有m个人手里拿着一张面值50元的钞票,有n个人手里
拿着一张面值100元的钞票。工作人员事先忘了为售票处准备任何零钱,请问您是否能算出这(m+n)个人共
有多少种排队方式买票,使售票处不至于出现找不开钱的尴尬局面?

如果说当m=n时,恰好是个Catalan数问题。并且可以轻松转化成2进制串01限制问题,和方格对角
限制走法问题。但是由于题目中并没有说明m一定等于n,所以本问题要再复杂一点?
zb
想当与从(0,0)出发到(m,n)点,不能穿过y=x这条直线,但可以在直线上,那么我们把这条直线向下移动一个单位,从而找到(0,0)的对称点(-1,-1),而(-1,-1)要到(m,n)则必须经过y=x-1,而从(0,0)出发经过Y=x-1这是不满足的,可以容易得到(0,0) –> y=x-1 –> (m,n) 与 (-1,-1)–> y=x-1 –> (m.n)的步数是向对应的,那么我吗用总数减去(-1,-1)–> (m.n)即可
答案:
C(m+n,m) – C(m+n,m-1)
=(n+m)![1/m!n! – 1/(m-1)!(n+1)!]
=(n+m)!(n-m+1) / (n!)*(m!))
当n==m的时候就刚好为catalan数
ps(可以用递归表示)

六、母函数:(普通型母函数 指数型母函数)
普通型母函数:普通型母函数用来解决多重集合的组合问题
定义:
对于任意数列a0,a1,a2…an 即用如下方法与一个函数联系起来:
~G(x) = a0 + a1*x + a2*x^2 + a3*x^3 +….+ an*x^n
则称G(x)是数列的生成函数(generating function)

典型例子:(1+x)^n
A(x) = (1+x)^n== ~ C(n,0),C(n,1),C(n,2),C(n,3),…..,C(n,n)

eg:
有 1、2、3、4克砝码各2个,称出7克物体的砝码选择方式有多少种?一共可以称出的质量有多少种?

现在考虑用母函数来解决这个问题:
我们假设x表示砝码,x的指数表示砝码的重量,这样:
2个1克的砝码可以用函数1+x+x^2表示,
2个2克的砝码可以用函数1+x^2+x^4表示,
2个3克的砝码可以用函数1+x^3+x^6表示,
2个4克的砝码可以用函数1+x^4+x^8表示,

“把组合问题的加法法则和幂级数的t的乘幂的相加对应起来“
1+x+x^2表示了两种情况:1表示质量为1的砝码取0个的情况,x表示质量为1的砝码取1个的情况。x^2表示质量为1的砝码取2个的情况。

x表示砝码,x的指数表示砝码的重量!2个1克的砝码,就可以称出0,1,2这三种质量的物品。他们分别只有一种方法。(理解??)
所以,几种砝码的组合可以称重的情况,可以用以上几个函数的乘积表示:
(1+x+x^2)*(1+x^2+x^4)*(1+x^3+x^6)*(1+x^4+x^8)

类似题目:

http://acm.hdu.edu.cn/showproblem.php?pid=4815    (2013 Asia Regional Changchun)

题意:有A,B两个人答题比赛。一共有N道题目(N<=40)每道题有一个分值point(0<point[i]>point;//第一个输入分数得分情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#define N 50000 LL a[N]; int n; double p;
LL maxn; //得到最大的分数,及X的最大次幂
void xishu() { //a[i],a[i]是系数,i是得的分数!     a[0]=1;
     int point;
     cin>>point; //第一个输入分数得分情况     a[point]++;
     maxn=point;
     for ( int j=1; j<n; j++) {
         cin>>point;
         for ( int i=maxn; i>0; i--) {
             if (a[i]) a[i+point]+=a[i]; //这里代表,幂为i的X系数不为零,则与幂为i+point的             if (i+point>maxn) maxn=i+point; //maxn表示X最高次幂,也就指是最多获得分数         }
         a[point]++;
     }
}
  int main() {
     int t;
     cin>>t;
     while (t--) {
         memset (a,0, sizeof (a));
         scanf ( "%d%lf" ,&n,&p);
         xishu();
         LL sum1=0,sum2=0;
         for ( int i=0; i<=maxn; i++) {
             sum1+=a[i];
         }
         for ( int i=0; i<=maxn; i++) {
             sum2+=a[i];
             if (sum2*1.0/sum1>=p) {
                 cout<<i<<endl;
                 break ;
             }
         }
     }
     return 0;
}

整数拆分问题:
(一)方法一——递归法
根据n和m的关系,考虑下面几种情况:
(1)当n=1时,不论m的值为多少(m > 0),只有一种划分,即{1};
(2)当m=1时,不论n的值为多少(n > 0),只有一种划分,即{1,1,….1,1,1};

(3)当n=m时,根据划分中是否包含n,可以分为两种情况:
(a)划分中包含n的情况,只有一个,即{n};
(b)划分中不包含n的情况,这时划分中最大的数字也一定比n小,
即n的所有(n-1)划分;
因此,f(n,n) = 1 + f(n, n – 1)。
(4)当nm时,根据划分中是否包含m,可以分为两种情况:
(a)划分中包含m的情况,即{m,{x1,x2,x3,…,xi}},其中{x1,x2,x3,…,xi}的和为n-m, 可能再次出现m,因此是(n-m)的m划分,因此这种划分个数为f(n-m, m);
(b)划分中不包含m的情况,则划分中所有值都比m小,即n的(m-1)划分, 个数为f(n, m – 1);
因此,f(n,m) = f(n – m,m) + f(n, m – 1)。
综合以上各种情况,可以看出,上面的结论具有递归定义的特征,其中(1)和(2)属于回归条件,(3)和(4)属于特殊情况,而情况(5)为通用情况,属于递归的方法,其本质主要是通过减少n或m以达到回归条件,从而解决问题。
其递归表达式如下所示:
整数拆分

递归代码:

1
2
3
4
5
6
int fun( int n, int m) {
     if (n==1||m==1) return 1;
     else if (n<m) return fun(n,n);
     else if (n==m) return 1+fun(n,n-1);
     else return fun(n,m-1)+fun(n-m,m);
}

(二)方法二——母函数
我们从整数划分考虑,假设n的某个划分中,1的出现个数记为a1,2的个数记为a2,…..,i的个数记为ai,显然有::ak <= n/k(0<= k <=n)因此n的划分数f(n,n),也就是从1到n这n个数字抽取这样的组合,每个数字理论上可以无限重复出现,即个数随意,使它们的综合为n。显然,数字i可以有如下可能,出现0次(即不出现),1次,2次,……,k次等等。把数字i用(x^i)表示,出现k次的数字i用(x^(i*k))表示,不出现用1表示。
例如,数字2用x^2表示,2个2用x^4表示,3个2用x^6表示,k个2用x^2k表示。则对于从1到N的所有可能组合结果我们可以表示为:
G(x) = ( 1 + x + x^2 + x^3 + … + x^n)*(1 + x^2 + x^4 + x^6 + ….)….(1 + x^n)
= g(x,1)*g(x,2)*g(x,3)*….*g(x,n)
= a0 + a1*x + a2*x^2 +…+ an*x^n + ….//展开式
上面的表达式中,每个括号内的多项式代表了数字i的参与到划分中的所有可能情况。因此,该多项式展开后,由于x^a *x^b = x^(a+b),因此x^i就代表了i的划分,展开后(x^i)项的系数也就是i的所有划分个数,即f(n,n) = an。
由此我们找到了关于整数划分的母函数G(x);剩下的问题就是,我们需要求出G(x)的展开后的所有系数。为此,我们首先要做多项式乘法,对于我们来说,并不困难。我们把一个关于x的多项式用一个整数数组a[]表示,a[i]代表x^i的系数,即:
g(x) = a[0] + a[1]x + a[2]x^2 + … + a[n]x^n;

例题:
http://acm.hdu.edu.cn/showproblem.php?pid=1028

题意就把一个整数拆成若干个数相加,问有多少种可能
法一
用放球问题可以解决
方法简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//n个球放到n个盒子里面盒子可以为空
//a[n][m]表示m个相同球放在n个相同盒子里(盒子至少有一个球) LL a[2000][2000]; void init() {
     a[0][0]=0;
     for ( int i=1; i<2000; i++) {
         a[i][1]=1;
         a[i][i]=1;
         a[i][0]=0;
     }
     for ( int i=2; i<2000; i++) {
         for ( int j=2; j<i; j++) {
             a[i][j]=a[i-1][j-1]+a[i-j][j];
         }
     }
} //a[n+m][m] 表示m个相同球放在n个相同盒子里(盒子可以为空) int main() {
     int n;
     init();
     while (cin>>n) {
         cout<<a[n+n][n]<<endl;
     }
     return 0;
}

法二母函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#define N 130    //最高次数 LL a[N];
LL b[N];
LL c[N]; //保存结果(a*b==c)
//两个多项式进行乘法,系数分别保存在a和b中,结果保存到c,项的最大次数到 N
void poly() {
     int i;
     int j;
     memset (c,0, sizeof (c));
     for (i=0; i<N; i++) {
         for (j=0; j<N-i; j++) {
             c[i+j]+=a[i]*b[j];
         }
     }
}
  void init( int m) {
     memset (a,0, sizeof (a));
     memset (b,0, sizeof (b));
     int i,j;
     //g(x)=x^0 + x^1 + x^2.....     for (i=0; i<N; i++) {
         a[i]=1;
     }
     //for(j = 2; j <= N; j++)//只能求f(n,n)     //通过修改这里,使得可以求f(n,m),对于任意的正整数n,m都适合     for (j=2; j<=m; j++) {
         memset (b,0, sizeof (b));
         for (i=0; i<N; i+=j) {
             b[i]=1;
         }
         poly();
         for (i=0; i<N; i++) {
             a[i]=c[i];
         }
     }
}
  int main() {
     int n;
     while (cin>>n) {
         int m;
         cin>>m;
         init(m);
         cout<<c[n]<<endl;
     }
     return 0;
}

七、Stirling数(第一类Stirling数)和(第二类Stirling数)

第一类Stirling数是有正负的,其绝对值是包含n个元素的集合分作k个环排列的方法数目。

递推公式为,
S(n,0) = 0, S(1,1) = 1.
S(n+1,k) = S(n,k-1) + nS(n,k)。

边界条件:
S(0 , 0) = 1
S(p , 0) = 0 p>=1
S(p , p) =1 p>=0
一些性质:
S(p ,1) = 1 p>=1
S(p, 2) = 2^(p-1)– 1 p>=2

第二类Stirling数是把包含n个元素的集合划分为正好k个非空子集的方法的数目。

递推公式为:
S(n,k)=0; (n=k>=1,且盒子不允许为空)的方案数就是stirling数.(即含 n 个元素的集合划分为 k 个集合的情况数)

递推公式:
递推公式为:
S(n,k)=0; (n=k>=1,且盒子不允许为空)的方案数就是stirling数.(即含 n 个元素的集合划分为 k 个集合的情况数)

递推公式:
  S(n,0) = 0
  S(n,1) = 1 (k = 1)
  S(n,n) = 1
  S(n,k) = 0 (k > n)
  S(n,k) = S(n-1,k-1)+k*S(n-1,k) (n >= k >= 2)

分析:设有n个不同的球,分别用b1,b2,…,bn表示。从中取出一个球bn,bn的放法有以下两种:
1. bn独占一个盒子,那么剩下的球只能放在k-1个盒子里,方案数为S(n-1,k-1);
2. bn与别的球共占一个盒子,那么可以将b1,b2,…,bn-1这n-1个球放入k个盒子里,
然后将bn放入其中一个盒子中,方案数为k*S(n – 1,k).

八、鸽巢原理也叫抽屉原理
简单形式是 :若有n个笼子和n+1只鸽子,所有的鸽子都被关在鸽笼里,那么至少有一个笼子有至少2只鸽子。
推广:一种表达是这样的:如果要把n个物件分配到m个容器中,必有至少一个容器容纳至少⌈n / m⌉个物件。(⌈x⌉向上取整)

此条目是由 todayac发表在 acm专题分类目录的。将 固定链接加入收藏夹
  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值