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定理
-
定義:對於一個給定的兩個整m,n>=2,則一定存在一個最小整數r,使得用兩種顏色(例如紅藍)無論給Kr的每條邊如何染色,總能找到一個紅色的Km或者藍色的Kn。顯然,當p>=r的時候,Kp也滿足這個性質。
-
表示形式:r可以看做一個有關m,n的二元函數,即r(m,n)。r(3,3)=6.
-
性質:
①等價性 r(m,n)=r(n,m)
②r(2,n)=n k2較特殊 只有一條邊 最小的kr為Kn
③r(m,2)=m
-
數表:
例題
問題很顯然,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+C1nan−1b+⋯+Cknan−kbk+⋯+Cnnbn(n∈N∗)
二項式定理通項:Tk+1=Cknan−kbk
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數定理:
邊界條件:
-
:有 n 個人和 n 個圓,每個圓只有一個人
-
:如果至少有 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)
綜上,可得出第二類斯特林數定理:
邊界條件:
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]); } } };