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注意开long long(尽管数据不大) #include
#include #include 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 ) { scanf("%d",&a[i]); max_=max(max_,a[i]); } for(i=0;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不合法的个数。
#includeusing 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; }
这道题就是鸽巢原理的运用。
#includeusing 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) cout< " "; cout<endl; break; } else if(vis[t]) { for(ll j=vis[t]+1;j) cout< " "; cout << i << endl; break; } vis[t] = i; } } return 0; }
这道题用到的是Ramsey定理
#includeusing 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 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)表示。
例:
假设有这样一个问题:现在有甲、乙、丙、丁4个小朋友,老师想要从中挑出2个小朋友排成一-列参加比赛 ,有几种排法?很容易枚举出来,有以下12种排法
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#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#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 ) { kk=t; for(j=0;j ) q[t++]=p[i]*q[j]*-1; } for(i=1;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
将分子和分母因式分解,上下约分后再使用高精度算法乘得最终的解。
这道题有点难想。
#includeusing 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; } } }