1.二项式定理
这个就是二项式定理的重要公式,我们的二项式定理的每一项的系数,代表的意思为从n个里面选出k个 ,以下是来自于百度百科上面的解释(原谅我实在不会数学定义)
因此我们可以去讨论二项式定理中的最特殊的一种形式——杨辉三角
假设我们的a,b都是1,那么式子就是(1+1)^n,我们可以得出杨辉三角的形状
这样就是我们的杨辉三角,我们可以发现每一层的和恰好就是(1+1)^ i,(杨辉三角从第0层开始的)
如果说,我们将元素都移到最左边对齐,我们会发现一个结论,著名的组合恒等式
也就是说 ,我们第i层第j列的元素,就是由其正上方的元素和正上方左边一个的元素累加得到
我们在处理二项式定理的问题的时候,最容易碰到的就是求C(n,k)的问题(就是从n个里面选k个的问题),我们有两种解决思路(当然了这种问题多半会有取模,因为数据很大,我们每个位置都会对数据进行取模)
第一种:我们可以通过打表的方法去打出杨辉三角的二维数组取模表,但是这种方法的时间复杂度高达O(n^2),只适用于小数据的问题
第二种:我们可以建立一个阶乘取模表,f[i]表示i的阶乘取模后的值是多少,然后建立一个逆元阶乘表,inv[i]表示i的阶乘的乘法逆元是多少,处理C(n,k)的问题直接调用公式即可.时间复杂度仅为O(n)
f[i]*inv[k]%mod*inv[n-k]%mod;
2.二项式定理的证明
以下是直接从左神的视频上截的图,因为自己证明写起来太麻烦了,我也是比较懒的
3.例题
P1313 [NOIP2011 提高组] 计算系数
怎么说呢?二项式定理水题吧,数据也没那么大,可以用上述说明的两种方法,先去求前面那个二项式系数C(k,m),然后快速幂去求a的n次方再乘以b的m次方,然后中间取模就好
#include<bits/stdc++.h>
using namespace std;
#define int long long
int a,b,k,n,m;
int f[1005][1005];
int mod=10007;
int fast(int a, int b)
{long long result = 1;long long base = a;while (b > 0) {if (b % 2 == 1) {result = (result * base) % mod;}base = (base * base) % mod;b /= 2;}return result;
}signed main()
{cin>>a>>b>>k>>n>>m;for(int i=0;i<=1000;i++){f[i][0]=1;}for(int i=1;i<=1000;i++){for(int j=1;j<=i;j++){f[i][j]=(f[i-1][j]+f[i-1][j-1])%mod;}}cout<<f[k][m]*fast(a,n)%mod*fast(b,m)%mod;return 0;
}
P2822 [NOIP2016 提高组] 组合数问题
这题还是比较有意思的,一开始写的纯暴力,没有进行优化,导致了有些点T了,后面用了前缀和去优化才过的,我们看题可以发现,我们要寻找的是取模能够被k整除的,因此,我们在一开始打杨辉三角的表的时候,我们需要对每一个位置上的数都去取模k,然后开一个新的二维数组,如果当前杨辉三角的位置是0,那么那个记录数组的值就是1,否则就是0,然后我们仔细看题会发现,其实他给的数据已经规定好了去查找的矩形的右下角的位置,因此我们只需要用前缀和去处理一下值即可;
还有一个要注意的点就是,不是他给的m就一定可以达到的,如果m>n,那么右下角位置就是s[n][n],否则就是s[n][m];
#include<bits/stdc++.h>
using namespace std;
#define int long long
int t,k;
int n,m;
int f[2005][2005];
int c[2005][2005];
int s[2005][2005];//二维前缀和
void solve()
{cin>>n>>m;if(m>n){cout<<s[n][n]<<"\n";}elsecout<<s[n][m]<<"\n";
}signed main()
{cin>>t>>k;f[0][0]=1;for(int i=1;i<=2000;i++){f[i][0]=1;}for(int i=1;i<=2000;i++){for(int j=1;j<=i;j++){f[i][j]=(f[i-1][j]+f[i-1][j-1])%k;}}for(int i=1;i<=2000;i++){for(int j=1;j<=i;j++){if(f[i][j]==0){c[i][j]=1;}else{c[i][j]=0;}}}for(int i=1;i<=2000;i++){for(int j=1;j<=2000;j++){s[i][j]=c[i][j]+s[i-1][j]+s[i][j-1]-s[i-1][j-1];}}while(t--){solve();}return 0;
}
3251. 单调数组对的数目 II
怎么说呢?这题其实来自于一个大厂面试题,当时数据范围已经卡死在n最大可以为1e7,因此当时那个题目必须要用到O(n)的时间复杂的算法,因此我们就用那题的数据来处理这个问题,这样以后碰到,心里就不会发怵了
这题我也是看到很多大犇都是用前缀和优化dp去解决的,但是我这个蒟蒻dp实在是太垃圾了,因此还是选择的用组合数学去实现的O(n)的时间复杂度的计算
我们假设一种特殊情况—— 数组里面的值都是同一个值
假设,数组里面都是同一个值,那么如果分割的时候,前面分割的值变大,那么后面那个数组能得到的值一定是变小的,因此我们可以将其变成图像来更好的处理
我们发现,我们的最大值能取到的就是5,横着最大能取到的就是4,也就是说,由于每个数都是一样的,所以我们最多走到(4,5)那个点,因此,最多走9步 ,我们在这9步中只有4步是向右走,因此我们的答案就是C(9,4)(从9个里面去选4个)
但是当我们如果并不是同一个数呢?我们该想一想这个问题
我们首先要确定边界,我们的边界就是最后一个数,也就是nums[n-1](n是数组的长度),然后我们就该分析当前的数大于,等于,小于的时候分别会产生哪些后果。
假设a数组是第一个数组(非递减数组),b数组是第二个数组(非递增数组)
我们已知:a[i]>a[i-1]
因此:nums[i]-a[i]>=nums[i-1]-a[i-1]
得:a[i]>=max(a[i-1],nums[i]-nums[i-1]+a[i-1])
因此我们会发现如果当前位大于前一位的话,会导致必须要强行向上移动d步,d=nums[i]-nums[i-1]
如果强行移动的步数,大于标准m的话,那么将m归0
计算的时候计算C(m+n,n);
但是我们会发现这题数据非常的大,因此我们需要用到第二种方法,计算阶乘表,以及阶乘逆元表,然后就可以愉快的ac掉这个题了
long long f[3005];
long long inv[3005];
long long mod=1e9+7;
long long fast(int a, int b)
{long long result = 1;long long base = a;while (b > 0) {if (b % 2 == 1) {result = (result * base) % mod;}base = (base * base) % mod;b /= 2;}return result;
}void ini()
{long long flag=1;f[0]=1;for(long long i=1;i<=3000;i++){f[i]=(f[i-1]*i)%mod;}inv[3000]=fast(f[3000],mod-2);for(long long i=3000;i>0;i--){inv[i-1]=(inv[i]*i)%mod;}return;
}long long solve(int n,int k)
{return f[n]*inv[k]%mod*inv[n-k]%mod;
}class Solution {
public:long long countOfPairs(vector<int>& nums) {ini();long long len=nums.size();long long m=nums[len-1];for(int i=1;i<len;i++){m-=max((long long)(nums[i]-nums[i-1]),0LL);if(m<0){return 0;}}long long ans=solve(m+len,len);return ans;}
};