二项式定理学习

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;
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值