华为笔试题里出现了组合数计算的问题,在此记录一下解法:
求的核心公式是
这个公式可以从直观上很好地理解:从n个物品里选k个的方法数,等于 选第n个物品,在前n-1个物品里选k-1个的方法数 + 不选第n个物品,在前n-1个物品里选k个的方法数。也可以从组合数的计算公式出发证明。
那么求组合数的问题就变成了动态规划问题:
这样计算可以有效避免求组合数时乘法溢出。
如果只需要求固定n的,那么可以进一步优化空间到一维: ,注意 j 要从右向左更新。
int cnk[MAXN] = {0};
cnk[0] = cnk[1] = 1;
for(int i = 2; i <= k; i++)
{
for(int j = i; j >= 1; j--)
{
cnk[j] = (cnk[j-1] + cnk[j]) % MOD;
}
}
两道例题:
1. 计算系数
题目大意:给定正整数a, b, k, n, m。求出 中, 的系数,结果对10007取模。
根据二项式定理, ,对比一下就知道,的系数是 。
其中,用快速幂去求,组合数用上面的递推公式去求。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int MOD = 10007;
ll cnk[1001] = {0};
int a, b, k, n, m;
ll multi(ll x, ll y)
{
return (x % MOD) * (y % MOD) % MOD;
}
ll fastpow(ll x, int exp)
{
if(exp == 0 || x == 1) return 1;
if(exp & 1) return (x % MOD * (fastpow(x, exp - 1) % MOD)) % MOD;
else
{
ll t = fastpow(x, exp / 2);
return (t % MOD) * (t % MOD) % MOD;
}
}
int main()
{
scanf("%d%d%d%d%d", &a, &b, &k, &n, &m);
cnk[0] = cnk[1] = 1;
for(int i = 2; i <= k; i++)
{
for(int j = i; j >= 1; j--)
{
cnk[j] = (cnk[j-1] + cnk[j]) % MOD;
}
}
printf("%lld", multi(multi(cnk[k-n], fastpow(a, n)), fastpow(b, k-n)));
return 0;
}
2. 5502. 将子数组重新排序得到同一个二叉查找树的方案数
给你一个数组 nums 表示 1 到 n 的一个排列。我们按照元素在 nums 中的顺序依次插入一个初始为空的二叉查找树(BST)。请你统计将 nums 重新排序后,统计满足如下条件的方案数:重排后得到的二叉查找树与 nums 原本数字顺序得到的二叉查找树相同。
比方说,给你 nums = [2,1,3],我们得到一棵 2 为根,1 为左孩子,3 为右孩子的树。数组 [2,3,1] 也能得到相同的 BST,但 [3,2,1] 会得到一棵不同的 BST 。
请你返回重排 nums 后,与原数组 nums 得到相同二叉查找树的方案数。
由于答案可能会很大,请将结果对 10^9 + 7 取余数。
给定数组[2,1,3],那么根节点是2,不能动,它的左子树节点集合是 l, 右子树节点集合是r,那么可以形成相同的树的排列总数就是 形成与l相同的树的排列总数 * 形成与 r 相同的排列总数 * C(l的节点数 + r的节点数, l的节点数)。最后一个组合数的意思是,除了根节点的位置不能动之外,剩下的位置中,可以任意选出一些位置来放置l中的节点。
class Solution {
private:
long long cnk[1001][1001] = {0};
int MOD = 1e9 + 7;
int helper(const vector<int>& nums)
{
if(nums.size() <= 1) return 1;
vector<int> l, r;
for(int i = 1; i < nums.size(); i++)
{
if(nums[i] < nums[0]) l.push_back(nums[i]);
else r.push_back(nums[i]);
}
long long ans = cnk[l.size() + r.size()][l.size()];
ans = (ans * helper(l)) % MOD;
ans = (ans * helper(r)) % MOD;
return ans;
}
public:
int numOfWays(vector<int>& nums) {
int n = nums.size();
cnk[1][0] = cnk[1][1] = 1;
for(int i = 2; i <= n; i++) cnk[i][0] = 1;
for(int i = 2; i <= n; i++)
{
for(int j = 1; j <= i; j++)
{
cnk[i][j] = (cnk[i-1][j-1] + cnk[i-1][j]) % MOD;
}
}
return helper(nums) - 1;
}
};