第五關——數論:組合數學


20:44:00 你在台上唱着我的創作,布局謀篇像本悲情小說——許嵩《最佳歌手》

我的寒假,我美好的寒假啊啊啊

“其實我還蠻不想寫你的,博客,可是沒辦法啊,誰叫我的寒假不要我了,我就只好要你了,博客”

目錄

  • 鴿巢原理

  • 鴿巢原理推廣

  • 楊輝三角和二項式系數

  • 容斥定理

  • 卡特蘭數

  • 斯特林數

那接下來就要來看一下鴿巢原理(抽屜原理)

也不知道發現它的人是不是看着別人鴿子的窩盯半天才發現的,人家鴿子會不好意思的啦!

  • 定義:如果有n+1個鴿子要進n個鴿巢,則至少存在一個鴿巢種包含兩個或更多的鴿子。

  • 例題:

    (2010問題求解3)記T為一隊列,初始時為空,現有n個總和不超過32的正整數依次入隊。如果無論這些數具體為何值,都能找到一種出隊的方式,使得存在某個時刻隊列T中的數之和恰好為9,那么n的最小值是_________。

    由題意可知bi取值范圍為1-32,現將這32個數構造為集合{1,10},{2,11}, …, {8,17}, {18,27}, {19,28},…,{23,32} ,{24},{25},{26},這17個集合中的任一個集合不能包含兩個或兩個以上的 ,否則它們的差為9,故由鴿巢定理可得出,n的最小值為18.
  • 應用:

    在邊長為1的正方形內任取5點,則其中至少有2點的距離不超過√2/2

  • 例題:

    吃糖果

    這道題是典型的鴿巢原理,可用鴿巢原理一種簡單的推理方法隔板法進行分析,如果S<N-1.把S個糖果放到隔板之間,這N個隔板不夠放.必然至少有兩個隔板之間沒有糖果,由於這兩個隔板是同一種糖果,所以無解。反之則有解。

    注意開long long(盡管數據不大)

    #include <cstdio>
    #include<math.h>
    #include<algorithm>
     using namespace std;
    int main()
    {
        int i,j,n,sum,max_,t;
        scanf("%d",&t);
        {
            while(t--)
            {
                sum=max_=0;
                scanf("%d",&n);
                int *a=(int*)malloc(sizeof(int)*n);
                for(i=0;i<n;i++)
                {
                    scanf("%d",&a[i]);
                    max_=max(max_,a[i]);
                }
                
                for(i=0;i<n;i++)
                {
                    if(a[i]!=max_) sum+=a[i];
                    if(sum>=max_-1) break;
                }
                
                if(i==n) printf("No\n"); 
                else printf("Yes\n");
            }
        }
    }

鴿巢原理推廣

  • 鴿巢原理的加強形式

定理: 令q1, q2, q3, ...., qn為正整數。如果將 q1, q2, q3, ...., qn - n + 1 個物體放到n個盒子中,則存在一個i,使得第i個盒子至少含有qi個物品 ;

證明: 假設將q1, q2, q3, ...., qn - n + 1個物品分別放到n個盒子里。如果每一個i (i = {1, 2, ..n}),第i個盒子中放少於qi 個物品,則所有盒子所放物品的總數不超過

    (q1 - 1) + (q2 - 1) + ... + (qn - 1) = q1 + q2 + ... + qn - n

顯然,比所要放的總數少一個。因此可以確定,對某個i (i = {1, 2, .. n}),第i個盒子至少包含qi個物品。

  • Erdös-Szekeres定理

簡單來說呢,就是在由n2+1n2+1個實數構成的序列中,必然含有長為n+1n+1的單調(增或減)子序列。

  • Ramsey定理

  1. 定義:對於一個給定的兩個整m,n>=2,則一定存在一個最小整數r,使得用兩種顏色(例如紅藍)無論給Kr的每條邊如何染色,總能找到一個紅色的Km或者藍色的Kn。顯然,當p>=r的時候,Kp也滿足這個性質。

  2. 表示形式:r可以看做一個有關m,n的二元函數,即r(m,n)。r(3,3)=6.

  3. 性質:

    ①等價性 r(m,n)=r(n,m)

    ②r(2,n)=n k2較特殊 只有一條邊 最小的kr為Kn

    ③r(m,2)=m

  4. 數表:

          

例題

問題很顯然,6以及6以上的直接統計345的暴力統計就行了  

就是說ans=3的個數+4的個數+5的個數+C(n,6)C(n,7)+…………+C(n,n)=2^n-C(n,0)-C(n,1)-C(n,2)-3不合法的個數-4不合法的個數-5不合法的個數。

#include<bits/stdc++.h>
using namespace std;
const int maxn=55;
const int mod=1000000007;
int T;
int a[maxn][maxn];
int cc=1;
int m,n;
void input(){
    scanf("%d%d",&n,&m);
    for (int i=1;i<=m;i++){
        int u,v;
        scanf("%d%d",&u,&v);
        a[u][v]=a[v][u]=1;
    }
}
bool ch(int x,int y,int z){
    int t=a[x][y]+a[x][z]+a[y][z];
    if (t==0||t==3)return true;
    return false;
}
bool h(int x,int y,int z,int w){
    return (ch(x,y,z)||ch(x,y,w)||ch(x,z,w)||ch(y,z,w));
}
bool e(int a,int b,int c,int d,int e){
    return (ch(a,b,c)||ch(a,b,d)||ch(a,b,e)||ch(a,c,d)||ch(a,c,e)||ch(a,d,e)||ch(b,c,d)||ch(b,c,e)||ch(b,d,e)||ch(c,d,e));
}
void solve(){
    long long ans=1;
    for (int i=1;i<=n;i++){
        ans = ans*2;
        ans%=mod;
    }
    ans-=n,ans--;
    ans-=n*(n-1)/2;
    ans+=mod;
    ans%=mod;
    for(int i=1;i<=n;i++)
    for(int j=i+1;j<=n;j++)
    for(int k=j+1;k<=n;k++)
    if(!ch(i,j,k))ans--;
    for(int i=1;i<=n;i++)
    for(int j=i+1;j<=n;j++)
    for(int k=j+1;k<=n;k++)
    for(int l=k+1;l<=n;l++)
    if(!h(i,j,k,l))ans--;
    for(int i=1;i<=n;i++)
    for(int j=i+1;j<=n;j++)
    for(int k=j+1;k<=n;k++)
    for(int l=k+1;l<=n;l++)
    for(int p=1+l;p<=n;p++)
    if(!e(i,j,k,l,p))ans--;
    ans+=mod;
    ans%=mod;
    printf("Case #%d: %lld\n",cc++,ans);
}
int main(){
    scanf("%d",&T);
    while (T--){
        memset(a,0,sizeof(a));
        scanf("%d%d",&n,&m);
        for(int i=1;i<=m;i++){
            int u,v;
            scanf("%d%d",&u,&v);
            a[u][v]=a[v][u]=1;
        }
        solve();
    } 
    return 0;
}

這道題就是鴿巢原理的運用。

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
const int mod=1e9+7;
typedef long long ll;
ll vis[maxn], a[maxn];
int main() {
    std::ios::sync_with_stdio(false);
    ll n, m;
    while(cin>>n>>m){
        if(!n&&!m){
            break;
        }
        ll sum=0,t;
        memset(vis,0,sizeof(vis) );
        for(ll i=1;i<=m;i++)
        cin>>a[i];
        for(ll i=1;i<=m;i++)
        {
            sum+=a[i];
            t=sum%n;
            if(t==0) 
            {
                for(ll j=1;j<i;j++)
                cout<<j<<" ";
                cout<<i<<endl;
                break;
            }
            else if(vis[t])
            {
                for(ll j=vis[t]+1;j<i;j++)
                cout<<j<<" ";
                cout << i << endl;
                break;
            }
            vis[t] = i;
        }
    }
    return 0;
}

這道題用到的是Ramsey定理

#include<bits/stdc++.h>
using namespace std;
int T,n;
int main() {
    scanf("%d",&T);
    while(T--) 
    {
        scanf("%d",&n);
        int a[10][10]={0};
        for(int i=1; i<n; ++i)
        for(int j=i+1; j<=n; ++j) 
        {
            int t;
            scanf("%d",&t);
            if(t&&n<6) a[i][j]=a[j][i]=1;
        }
        if(n>=6)
        {
            puts("Bad Team!");
            continue;
        }
        int f=0;
        for(int i=1;i<=n;++i)
        for(int j=i+1;j<=n;++j)
        for(int k=j+1;k<=n;++k)
        if(a[i][j]&&a[i][k]&&a[j][k])
        {
            f=1;
            break;
        }
        if(f) puts("Bad Team!");
        else puts("Great Team!");
    }
    return 0;
}

19:24:54 只有我守着安靜的沙漠,等待着花開。——華晨宇《煙火中的塵埃》

排列與組合

排列組合是組合學最基本的概念。所謂排列,就是指從給定個數的元素中取出指定個數的元素進行排序。組合則是指從給定個數的元素中僅僅取出指定個數的元素,不考慮排序。

排列的定義:從n個不同元素中,任取m(m≤n,m與n均為自然數,下同)個不同的元素按照一定的順序排成一列,叫做從n個不同元素中取出m個元素的一個排列;從n個不同元素中取出m(m≤n)個元素的所有排列的個數,叫做從n個不同元素中取出m個元素的排列數,用符號 A(n,m)表示。

計算公式:               (此外規定0! = 1)

例:

假設有這樣一個問題:現在有甲、乙、丙、丁4個小朋友,老師想要從中挑出2個小朋友排成一-列參加比賽 ,有幾種排法?很容易枚舉出來,有以下12種排法

甲乙,甲丁,甲丙,乙甲,乙丁,乙丙,丁甲,丁乙,丁丙,丙甲,丙乙,丙丁

組合的定義:從n個不同元素中,任取m(m≤n)個元素並成一組,叫做從n個不同元素中取出m個元素的一個組合;從n個不同元素中取出m(m≤n)個元素的所有組合的個數,叫做從n個不同元素中取出m個元素的組合數。用符號 C(n,m) 表示。

計算公式:

例:

如果老師只是想從4個小朋友中挑選2個參加比賽,並不考慮排隊的順序,那有多少種方法呢?同樣可以枚舉出來,有以下6種挑法:

甲乙,甲丁,甲丙,乙丁,乙丙,丁丙

22:19:37 如果這失憶變成了洪水,也對,也對。——鬼卞《失眠症》

楊輝三角和二項式系數

楊輝三角小的時候都學過,那么楊輝三角有些什么規律呢,大家也都知道,那么求楊輝三角除了遞推打表還有下面這種方法:

每一行從上一行推導而來。如果編程求楊輝三角第n行的數字,可以模擬這個推導過程,逐級遞

推,復雜度是O(n2)。不過,若改用數學公式計算,則可以直接得到結果,比用遞推快多了

,這個公式就是(1+x)n。

觀察(1+x)n的展開:

(1+x)0 = 1

(1+x)1 = 1+x

(1+x)2 = 1+2x+x2

(1+x)3 = 1+3x+3x2+x3

每一行展開的系數剛好對應楊輝三角每一行的數字。也就是說,楊輝三角可以用(1+x)n來定

義和計算。

二項式定理公式:(a+b)n=C0nan+C1nan1b++Cknankbk++Cnnbn(nN)

二項式定理通項:Tk+1=Cknankbk

19:41:02 如果放下是結局出口,我已無路可走。——孟凡明《某某》

容斥原理

在計數時,必須注意無一重復,無一遺漏。為了使重疊部分不被重復計算,人們研究出一種新的計數方法,這種方法的基本思想是:先不考慮重疊的情況,把包含於某內容中的所有對象的數目先計算出來,然后再把計數時重復計算的數目排斥出去,使得計算的結果既無遺漏又無重復,這種計數的方法稱為容斥原理。 

公式:

兩個集合的容斥關系公式:A∪B =|A∪B| = |A|+|B| - |A∩B |(∩:重合的部分)

三個集合的容斥關系公式:|A∪B∪C| = |A|+|B|+|C| - |A∩B| - |B∩C| - |C∩A| + |A∩B∩C|                                                       

例:

在1到1000的自然數中,能被3或5整除的數共有多少個?不能被3或5整除的數共有多少個?

分析:顯然,這是一個重復計數問題(當然,如果不怕麻煩你可以分別去數3的倍數,5的倍數)。我們可以把“能被3或5整除的數”分別看成A類元素和B類元素,能“同時被3或5整除的數(15的倍數)”就是被重復計算的數,即“既是A類又是B類的元素”。求的是“A類或B類元素個數”。我們還不能直接計算,必須先求出所需條件。1000÷3=333……1,能被3整除的數有333個(想一想,這是為什么?)同理,可以求出其他的條件。

整除

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

這道題要用到容斥原理,每個a[i]都有選和不選兩種可能,lcm的計算和容斥原理的實現可以用搜索來實現。時間復雜度為O(2^n)。

lcm的計算在代碼里用的是gcd算的。這道題由於數據比較大,所以要開long long。

需要注意dfs函數中的關系式,是容斥原理的關鍵。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll l,r,ans,a[21];
int n;
ll gcd(ll a,ll b)
{
    if(b==0)return a;
    gcd(b,a%b);
}
ll lcm(ll a,ll b)
{
    return (a*b)/gcd(a,b);
}
void dfs(int x,int y,long long z)
{
    if (x>n)
    {
        if (y%2==1) ans+=r/z-(l-1)/z;
        else ans-=r/z-(l-1)/z;
        return;
    }
    dfs(x+1,y+1,lcm(a[x],z));
    dfs(x+1,y,z);
}
int main()
{
    scanf("%d%lld%lld",&n,&l,&r);
    for (int i=1;i<=n;i++)
    scanf("%lld",&a[i]);
    dfs(1,1,1);
    printf("%lld",ans);
    return 0;
}

給n*m個點(1 ≤ m, n ≤ 1e5),左下角的點為(1,1),右上角的點(n,m),一個人站在(0,0)看這些點。在一條直線上,只能看到最前面的一個點,后面的被檔住看不到,求這個人能看到多少個點。

在同一條直線(y = kx (k為自然數))上的點只能看見最前面的 最前面的點的 y 和 x 肯定互質

所以就變成了 求m * n 這個區域中互質的 x 與 y 的對數

對於每一個1 ~ n 求 1 ~ m中有多少個與之互質的數  加起來就好了

#include<bits/stdc++.h>
#define ll long long
const int N=100010
int p[N];
int q[N];
int k;
void getp(int n)
{
    int i,j;
    k=0;
    for(i=2;i*i<=n;i++)
    {
        if(n%i==0)
        {
            p[k++]=i;
            while(n%i==0)
                n/=i;
        }
    }
    if(n>1)
        p[k++]=n;
}
int solve(int n)
{
    int i,j,kk,t=0;
    ll sum=0;
    q[t++]=-1;
    for(i=0;i<k;i++)
    {
        kk=t;
        for(j=0;j<kk;j++)
            q[t++]=p[i]*q[j]*-1;
    }
    for(i=1;i<t;i++)
        sum+=n/q[i];
    return sum;
}
int main()
{
    int t,n,m,i,j;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&m);
        ll ans=n;
        for(i=2;i<=m;i++)
        {
            getp(i);
            ans+=n-solve(n);
        }
        printf("%lld\n",ans);
    }
    return 0;
}

20:30:31 你能往前走 我也厭倦了再蹉跎,緊抱住的綠洲 是殘破的海市蜃樓——沈以誠《綠洲》

卡特蘭數

前幾項為 : 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(0)=1,h(1)=1,Catalan數滿足遞歸式:h(n) = h(0)*h(n-1) + h(1)*h(n-2) + ... + h(n-1)*h(0)  (n>=2)該遞推關系的解為:h(n) = C(2n,n)/(n+1),n=0,1,2,3,... (其中C(2n,n)表示2n個物品中取n個的組合數)常用遞推式:a[i,j]=a[i-1,j]+a[i,j-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,...)

二叉樹計數           出棧序列                  加括號               凸多邊形划分

這三道題均使用了卡特蘭數,都還比較簡單,只是需要把卡特蘭數的遞推公式求出來就行

Binary Tree Number

將分子和分母因式分解,上下約分后再使用高精度算法乘得最終的解。

這道題有點難想。

#include<bits/stdc++.h>
using namespace std;
int n,l=1;
int a[1000010];
void s(int x)
{
    for(int i=1;i<=l;i++) 
    a[i]*=x;
    for(int i=1;i<=l;i++)
    {
        a[i+1]+=a[i]/10;
        a[i]=a[i]%10;
    }
    while(a[l+1])
    {
        a[l+2]=a[l+1]/10;
        a[l+1]%=10;
        l++;
    }
    return;
}
void ss(int x)
{
    for(int i=l;i>=1;i--)
    {
        a[i-1]+=a[i]%x*10;
        a[i]/=x;
    }
    for(int i=l;i>=1;i--)
    {
        if(a[i]!=0)
        {
            l=i;
            return;
        }
    }
}
int main()
{
    cin>>n;
    a[l]=1;
    for(int i=1;i<=n;i++)
    {
        s(i*4-2);
        ss(i+1);
    }
    for(int i=l;i>=1;i--) 
    printf("%d",a[i]);
    return 0;
}

20:57:01 原諒我不可自拔,可能不經意看你一眼,百米沖刺都會停下。——沈以誠《形容》

斯特林數

第一類斯特林數
1.定理
第一類斯特林數 S1(n,m) 表示的是將 n 個不同元素構成 m 個圓排列的數目。

2.遞推式
設人被標上1,2,.....p,則將這 p 個人排成 m 個圓有兩種情況:在一個圓圈里只有標號為 p 的人自己,排法有 S1(n-1,m-1) 個。p 至少和另一個人在一個圓圈里。

這些排法通過把 1,2....n-1 排成 m 個圓再把 n 放在 1,2....n-1 任何一人左邊得到,因此第二種類型排法共有 (n-1)*S1(n-1,m) 種。

我們所做的就是把 {1,2,...,p} 划分到 k 個非空且不可區分的盒子,然后將每個盒子中的元素排成一個循環排列。

綜上,可得出第一類Stirling數定理:

S1(n,m)=(n-1)*S1(n-1,m)+S1(n-1,m-1) (n>1,m>1)

邊界條件:

  1. S1(n,m)=1 (n>=0):有 n 個人和 n 個圓,每個圓只有一個人

  2. S1(n,0)=0 (n>=1) :如果至少有 1 個人,那么任何的安排都至少包含一個圓

const int mod=1e9+7;//取模
LL s[N][N];//存放要求的第一類Stirling數
void init(){
    memset(s,0,sizeof(s));
    s[1][1]=1;
    for(int i=2;i<=N-1;i++){
        for(int j=1;j<=i;j++){
            s[i][j]=s[i-1][j-1]+(i-1)*s[i-1][j];
            if(s[i][j]>=mod)
                s[i][j]%=mod;
        }
    }

 

第二類斯特林數

1.定理
第二類斯特林數 S2(n,m) 表示的是把 n 個不同元素划分到 m 個集合的方案數。

2.遞推式
元素在哪些集合並不重要,唯一重要的是各集合里裝的是什么,而不管哪個集合裝了什么。

考慮將前 n 個正整數,{1,2,...,n} 的集合作為要被划分的集合,把 {1,2,...,n} 分到 m 個非空且不可區分的集合的划分有兩種情況:

那些使得 n 自己單獨在一個集合的划分,存在有 S2(n-1,m-1) 種划分個數
那些使得 n 不單獨自己在一個盒子的划分,存在有 m*S2(n-1,m) 種划分個數
考慮第二種情況,n 不單獨自己在一個盒子,也就是 n 和其他元素在一個集合里面,也就是說在沒有放 n 之前,有 n-1 個元素已經分到了m 個非空且不可區分的盒子里面(划分個數為 S2(n-1,m)),那么現在問題是把 n 放在哪個盒子里面,此時有 m 種選擇,所以存在有 m*S2(n-1,m)

綜上,可得出第二類斯特林數定理:
S2(n,m)=m*S2(n-1,m)+S2(n-1,m-1) (1<=m<=n-1)

邊界條件:\left\{\begin{matrix}S2(n,m)=1 ,n>=0 \\S2(n,0)=0,n>=1 \end{matrix}\right. 

const int mod=1e9+7;//取模
LL s[N][N];//存放要求的Stirling數
void init(){
    memset(s,0,sizeof(s));
    s[1][1]=1;
    for(int i=2;i<=N-1;i++){
        for(int j=1;j<=i;j++){
            s[i][j]=s[i-1][j-1]+j*s[i-1][j];
            if(s[i][j]>=mod)
                s[i][j]%=mod;
        }
    }
}

來源於百度百科

 高精度模板

const int BITLEN = 100000000, BIGSIZE = 1500;
struct Big{
    long long val[BIGSIZE], len;
    void operator = (long long x){
        memset(val, 0, sizeof(val));
        val[len = 1] = x;
        while(val[len] >= BITLEN)
            val[len+1] = val[len] / BITLEN, val[len] %= BITLEN, len++;
    }
    void read(){
        char inp[BIGSIZE * 8];
        int top = -1;
        memset(inp, '0', sizeof(inp));
        do inp[++top] = getchar();
        while(isdigit(inp[top]));
        inp[top] = '0';
        reverse(inp, inp+top);
        for(int i=1; (i-1)*8<top; i++, len++) for(int j=i*8; j>(i-1)*8; j--)
            val[i] = val[i] * 10 + (inp[j-1] ^ 48);
        len--;
    }
    Big(int x = 0){this->operator = (x);}
    Big operator + (Big x){
        Big ans;
        ans.len = max(len, x.len) + 1;
        for(int i=1; i<=ans.len; i++){
            ans.val[i] += val[i] + x.val[i];
            ans.val[i + 1] += ans.val[i] / BITLEN;
            ans.val[i] %= BITLEN;
        }
        while(ans.len > 1 && ans.val[ans.len] == 0)
            ans.len--;
        return ans;
    }
    Big operator - (Big x){
        Big ans;
        ans.len = max(len, x.len) + 1;
        for(int i=1; i<=ans.len; i++){
            ans.val[i] += val[i] - x.val[i] + BITLEN, ans.val[i + 1]--;
            ans.val[i + 1] += ans.val[i] / BITLEN;
            ans.val[i] %= BITLEN;
        }
        while(ans.len > 1 && ans.val[ans.len] == 0)
            ans.len--;
        return ans;
    }
    Big operator * (Big x){
        Big ans;
        ans.len = len + x.len;
        for(int i=1; i<=len; i++)
            for(int j=1; j<=x.len; j++)
                ans.val[i + j - 1] += val[i] * x.val[j];
        for(int i=1; i<=ans.len; i++)
            ans.val[i + 1] += ans.val[i] / BITLEN, ans.val[i] %= BITLEN;
        while(ans.len > 1 && ans.val[ans.len] == 0)
            ans.len--;
        return ans;
    }
    bool operator < (Big &temp){
        if(len != temp.len) return len < temp.len;
        for(int i=len-1; i>=1; i--) if(val[i] != temp.val[i])
            return val[i] < temp.val[i];
        return false;
    }
    void print(){
        for(int i=len; i>=1; i--){
            if(i != len) for(int j=BITLEN/10; j>1; j/=10) if(val[i] < j)
                putchar('0');
            printf("%d", val[i]);
        }
    }
};
View Code

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM