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

    一.鸽巢原理(抽屉原理)

                  1.定理:若有n个鸽巢, n+1只鸽子, 则至少有一个鸽巢里至少有两只鸽子。

                      2.例题:

                                  记T为一队列,初始时为空, 现有n个总和不超过32的正整数依次入队。如果 无论这些数具体为何值,都能找到一种出队的方 式,使得存在某个时刻队列T中的数之和恰好为9, 那么n的最小值是_________。

                       解析:队列中的数为 ak ,设队列的前缀和为 bk ,若要使数之和恰好为九,就需要找到一个 j 与 i ,使得 bj-bi=9。bi取值范围为1-32,现将这32个数构造为集合{1,10}, {2,11}, …, {8,17}, {18,27}, {19,28},…,{23,32} ,{24},{25},{26},一共17个集合。所以至少需要 18 个数字才能让有两个在同一个集合内,满足题意。

                                所以,答案为 18。

                       3.应用:

                         吃糖果(HDU 1205

                                HOHO,终于从Speakless手上赢走了所有的糖果,是Gardon吃糖 果时有个特殊的癖好,就是不喜欢将一样的糖果放在一起吃,喜 欢先吃一种,下一次吃另一种,这样;可是Gardon不知道是否存 在一种吃糖果的顺序使得他能把所有糖果都吃完?请你写个程序 帮忙计算一下。

                       思路:   

                                         可以用“隔板法”求解。找出最多的一种糖果, 把它的数量N看成N个隔板,隔成N个空间(把每个隔板的右边看成一 个空间);其 他所有糖果的数量为S。

                                  当s<N-1时,无解,因为此时必有两个“隔板”是相邻的,不满足题意,反之,当 s>=N-1 时,成立;

     1 #include<bits/stdc++.h>
     2 typedef long long ll;
     3 using namespace std;
     4 int main()
     5 {
     6     ll t,n,i,j,a,sum=0,maxx;
     7     scanf("%lld",&t);
     8     while(t--)
     9     {
    10         sum=0;
    11         maxx=0;
    12         scanf("%lld",&n);
    13         for(i=0;i<n;i++)
    14         {
    15             scanf("%lld",&a);
    16             sum+=a;
    17             if(maxx<a)
    18             maxx=a;
    19         }
    20      if(maxx<=sum-maxx+1)
    21      printf("Yes
    ");    
    22      else
    23      printf("No
    ");
    24      }
    25      return 0;
    26 }

                推广:

                             鸽巢n个, 鸽子m1+m2+…+mn-n+1只, 其中 m1,m2,…,mn, n都是正整数, 则存在一个 i (i={1,2,3,,,n}) ,使得第 i 个鸽巢的数目不小于 mi

                             否则, 总鸽子数<=(m1-1)+(m2-1)+…+(mn-1) 与总鸽子数为m1+m2+…+mn-n+1矛盾

                             Erdös-Szekeres定理

                                   定理: 在由n2+1个实数构成的序列中, 必然 含有长为n+1的单调(增或减)子序列

                         Ramsey定理

                                          Ramsey定理:对于一个给定的两个整m,n>=2,则一定存在一个 最小整数r,使得用两种颜色(例如红蓝)无论给Kr的每条边如 何染色,总能找到一个红色的Km或者蓝色的Kn。显然,当 p>=r的时候,Kp也满足这个性质。

                                  r可以看做一个有关m,n的二元函数,即r(m,n)。

                                  基本性质:
                                 ①等价性 r(m,n)=r(n,m)
                                  ②r(2,n)=n k2较特殊 只有一条边 最小的kr为Kn
                                  ③r(m,2)=m

                                    例题:
                                                    sum(HDU 5776)

                               题目大意    定一个数列,求是否存在连续子列和为m的倍数,存在输出YES,否则输出NO

               思路:
    预处理前缀和,一旦有两个数模m的值相同,说明中间一部分连续子列可以组成m的倍数。假设sum[1,i]%m=k,sum[1,j]%m=k,则sum[i+1,j]%m=0。

                                           这道题相当于把n个物品放进m个抽屉里,如果n>m,那么一定会出现两个前缀和%m==k,所以n>m直接输出yes。

         

     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 int vis[50005];
     4 int sum[100005];
     5 int main()
     6 {
     7     int t;
     8     scanf("%d",&t);
     9     while(t--)
    10     {
    11         int n,m;
    12         scanf("%d %d",&n,&m);
    13         memset(sum,0,sizeof(sum));
    14         memset(vis,0,sizeof(vis));
    15         for(int i=1;i<=n;i++)
    16         {
    17             int x;
    18             scanf("%d",&x);
    19             sum[i]=sum[i-1]+x;
    20             vis[sum[i]%m]++;
    21         }
    22         if(vis[0]>=1||n>m)
    23             printf("YES
    ");
    24         else
    25         {
    26             int flag=0;
    27             for(int i=1;i<=m;i++)
    28             {
    29                 if(vis[i]>=2)
    30                 {
    31                     flag=1;
    32                     break;
    33                 }
    34             }
    35             if(flag)
    36                 printf("YES
    ");
    37             else
    38                 printf("NO
    ");
    39         }
    40     }
    41 }

     二.杨辉三角 & 二项式定理

     1.二项式定理

                      

              观察计算我们可以得到:

     2.杨辉三角

                    将上图中的系数拆下来组成一个三角形就是杨辉三角了

    1

    1   1

    1   2   1

    1   3   3   1

    1   4   6   4   1

    ......

    三.容斥原理

           要计算几个集合并集的大小,我们要先将所有单个集合的大小计算出来,然后减去所有两个集合相交的部分,再加回所有三个集合相交的部分,再减去所有四个集合相交的部分.........依此类推,一直计算到所有集合相交的部分。(可以理解为就是先把所有单个集合全加一遍然后再去重)。

             Venn 图:

                

             例题:整除(cq11z online judge)      

    Time Limits: 1000 ms Memory Limits: 131072 KB Detailed Limits

    Description

    给出n个数a1,a2……an,求区间[L,R]中有多少个整数不能被其中任何一个数整除。

    Input

    第一行三个正整数,n,L,R。

    第二行n个正整数a1,a2……an

    Output

    一个数,即区间[L,R]中有多少个整数不能被其中任何一个数整除。

    Sample Input

    2 1 1000

    10 15

    Sample Output

    867

    Data Constraint

    对于30%的数据,1<=n<=10,1<=L,R<=1000

    对于100%的数据,1<=n<=18,1<=L,R<=10^9

    思路:容斥 ,由于n比较小,可以直接暴力dfs,看看每次选了几个数,奇数加偶数减统计答案 

     1 #include <bits/stdc++.h>
     2 using namespace std;
     3 typedef long long ll;
     4 
     5 ll l,r,ans,t[21];
     6 int n;
     7 
     8 ll gcd(ll a,ll b){
     9     if(b==0) return a;
    10     return gcd(b,a%b);
    11 }
    12 
    13 ll lcm(ll a,ll b){
    14     return (a*b)/gcd(a,b);
    15 } 
    16 
    17 void dfs(int depth,int a,ll b){
    18     if(depth>n){
    19         if(a&1) ans+=r/b-(l-1)/b;
    20         else ans-=r/b-(l-1)/b;
    21         return;
    22     }
    23     dfs(depth+1,a+1,lcm(t[depth],b));
    24     dfs(depth+1,a,b);
    25 }
    26 
    27 int main(){
    28     scanf("%d%lld%lld",&n,&l,&r);
    29     for(int i=1;i<=n;i++) scanf("%lld",&t[i]);
    30     dfs(1,1,1);
    31     printf("%lld",ans);
    32     return 0;
    33 }

     四.卡特兰数

              

                    应用:二叉树计数(cq11z online judge)

                        思路:f[i]表示i个相同结点构成的二叉树数量。 

                               选1个结点作为树根,选j个结点作为 树的左子树,剩余的i-j-1个结点作为树的右子树, 则左子树的形状有f[j]种,右子树的形状有f[i-j-1] 种,j可以从0取到i-1。 

                          可以得到如下递推关系式:

                                f[i]= 1  if i==0 ;

                                        ∑j=0i-1   f[j]*f[i-j-1];

     

     1 #include<iostream>
     2 #include<cstdio>
     3 #include<algorithm>
     4 #include<cstring>
     5 #include<string>
     6 #include<cmath>
     7 #include<vector>
     8 #include<stack>
     9 #include<queue>
    10 #include<climits>
    11 using namespace std;
    12 long long a[55] = {1};
    13 int main() {
    14     int n;
    15     scanf("%d",&n);
    16     for(int i=1; i<=n; i++)
    17         for(int j=0; j<=i; j++)
    18             a[i] += a[j] * a[i-j-1];
    19     printf("%lld",a[n]);
    20     return 0; 
    21 }

     五.Stirling数      

                    第一类Stirling数

                 定义第一类Stirling数s(n,k):把n个不同的元素分配到k个圆排列里,圆不能为空。问有 多少种分法?

                 第一类Stirling数的递推公式: s(n,k) = s(n-1, k-1) + (n-1) s(n-1,k) ,  1<=k <=n ;

                 第二类Stirling数
                 第二类斯特林数S(n,m)表示的是把n个不同的小球放在m个相同的盒子里,不能有空盒子 ,问有多少中分法  

                     递推:
                     s(n,m)=s(n−1,m−1)+ms(n−1,m) 

                  容斥原理:

                      

                    例题:

                         Count the Buildings 

                            思路:               

                                          由于高的楼会挡住低的楼,所以这些楼首先会被划分成f+b-2个区域(除去中间最高的楼),并且左边有f-1个,右边有b-1个。

                                      对于一个区域(假设在左边),这个区域由若干栋楼组成,并且最高的楼一定在最左边。

                                    那么,由一个区域中的元素组成的任意一个环排列,在这个区域中都有唯一的放法,因为要把最高的元素拉到最左边。

                                    所以,原题被简化为:将n-1个元素形成f+b-2个环排列,并将其中f-1个环放在左边的方法数。

                                       所以答案为:S(n-1,f+b-2)*C(f+b-2,f-1);

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    using namespace std;
    typedef long long ll;
    const int maxn = 2000+10;
    const int MOD = 1000000007;
    int n,f,b;
    ll s[maxn][maxn];
    ll c[maxn][maxn];
    void init(){
        c[0][0] = 1;
        s[0][0] = 1;
        for(int i = 1; i < maxn; i++){
            c[i][0] = c[i][i] = 1;
            s[i][0] = 0;s[i][i] = 1;
            for(int j = 1; j < i; j++){
                c[i][j] = (c[i-1][j] + c[i-1][j-1])%MOD; 
                s[i][j] = ((i-1)*s[i-1][j]+s[i-1][j-1])%MOD; 
            } 
        }
    }
    int main(){
        int ncase;
        init();
        cin >> ncase;
        while(ncase--){
            scanf("%d%d%d",&n,&f,&b); 
            if(f+b-2>n){
                puts("0");
            }else {
                ll ans = (c[f+b-2][f-1]*s[n-1][b+f-2])%MOD; 
                printf("%d
    ",ans);
            }
        }
        return 0;
    }
  • 相关阅读:
    mongodb
    python中读取文件的read、readline、readlines方法区别
    uva 129 Krypton Factor
    hdu 4734
    hdu 5182 PM2.5
    hdu 5179 beautiful number
    hdu 5178 pairs
    hdu 5176 The Experience of Love
    hdu 5175 Misaki's Kiss again
    hdu 5174 Ferries Wheel
  • 原文地址:https://www.cnblogs.com/lirh04/p/12253889.html
Copyright © 2011-2022 走看看