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";
}
else
cout<<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;
}
};