求组合数I II III IV
求组合数
求组合数1 递推 $ O(n^2) $
原题链接:https://www.acwing.com/problem/content/887/
思路
数据范围为2000,可以在\(n^2\)以内解决问题,就直接使用下面的递推即可
已知公式
就用此公式递推求即可
for(int i = 0; i < N; i ++)
{
for(int j = 0; j <= i; j ++)
{
if(j == 0) c[i][j] = 1;
else c[i][j] = (c[i-1][j] + c[i-1][j-1]) % mod;
}
}
代码:
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 2010;
const int mod = 1e9+7;
int c[N][N];
int n;
void init()
{
for(int i = 0; i < N; i ++)
{
for(int j = 0; j <= i; j ++)
{
if(j == 0) c[i][j] = 1;
else c[i][j] = (c[i-1][j] + c[i-1][j-1]) % mod ;
}
}
}
int main()
{
cin >> n;
init();
while(n --)
{
int a,b;
cin >> a >> b;
cout << c[a][b] << endl;
}
return 0;
}
求组合数2 \(O(nlog(n))\)
原题链接:https://www.acwing.com/problem/content/888/
逆元
如果 \(b*x \equiv 1 \: mod\: N\) 或者\(\frac{1}{b} \equiv x\: mod\: N\)
则称 x 为b模N的乘法逆元
思路
数据范围是100000,\(n^2\)时间肯定不行
可以根据以下公式预处理出来阶乘(分母上的阶乘使用快速幂求逆元),时间复杂度为\(nlog(n)\)
已知有公式
可以预处理出来阶乘,但是最后计算的时候不能直接除以相应的阶乘
因为:(a / b) % p 不等于(a % p) / (b % p),程序中算出来的每一个 i 的阶乘都是模了 mod 的,不能相除,只能转化为逆元求解
所以计算(a-b)!模N的乘法逆元和b!模N的乘法逆元,因为1e9+7是一个质数,比一个质数小的数都与这个数互质,所以直接用快速幂求逆元
阶乘的逆元的计算:(阶乘的逆元可以用前面的逆元来计算)
如果x 是 a 的逆元,y 是 b 的逆元,那么 ax≡1,by≡1ax≡1,by≡1,所以 abxy≡1,所以 xy 是 ab 的逆元。–yxc
思路实现
预处理阶乘
const int mod = 1e9+7; // 是一个质数
int fact[N],infact[N];
fact[0] = infact[0] = 1;
for(int i = 1; i < N; i ++)
{
fact[i] = (LL)fact[i-1] * i % mod;
infact[i] = (LL)infact[i-1] * qmi(i,mod - 2,mod) % mod;
}
快速幂公式
int qmi(int a,int k,int p)
{
int res = 1;
while(k)
{
if(k & 1) res = (LL)res * a % p;
a = (LL) a * a % p;
k >>= 1;
}
return res;
}
代码
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
const int N = 100010;
const int mod = 1e9 + 7;
int fact[N],infact[N];
int qmi(int a,int k,int p)
{
int res = 1;
while(k)
{
if(k & 1) res = (LL) res * a % p;
a = (LL) a * a % p;
k >>= 1;
}
return res;
}
int main()
{
fact[0] = infact[0] = 1;
for(int i = 1; i < N; i ++)
{
fact[i] = (LL)fact[i-1] * i % mod;
infact[i] = (LL)infact[i-1] * qmi(i,mod - 2,mod) % mod;
}
int n;
scanf("%d",&n);
while(n --)
{
int a,b;
scanf("%d%d",&a,&b);
printf("%d\n",(LL)fact[a] * infact[a-b] % mod * infact[b] % mod);
}
return 0;
}
求组合数3 \(O(p∗logn∗logp)\)
原题链接:https://www.acwing.com/problem/content/889/
卢卡斯定理lucas
使用卢卡斯定理,可以将时间复杂度变成\(p*logn*logp\)
当b < p && a < p的时候直接计算这个组合数即可
否则计算 \(C_{a\;mod\;p}^{b\;mod\;p} * lucas(C_{a/p}^{b/p})\)
代码
#include<iostream>
using namespace std;
typedef long long LL;
int qmi(int a,int k,int p)
{
int res = 1;
while(k)
{
if(k & 1) res = (LL) res * a % p;
a = (LL) a * a % p;
k >>= 1;
}
return res;
}
int C(int a,int b,int p)
{
if(b > a) return 0;
int res = 1;
for(int i = 1, j = a; i <= b; i ++,j --)
{
res = (LL)res * j % p;
res = (LL)res * qmi(i,p-2,p) % p;
}
return res;
}
int lucas(LL a, LL b, int p)
{
if(a < p && b < p) return C(a,b,p);
else return (LL)C(a % p,b % p,p) * lucas(a/p,b/p,p) % p;
}
int main()
{
int n;
cin >> n;
for(int i = 0; i < n; i ++)
{
LL a,b;
int p;
cin >> a >> b >> p;
cout << lucas(a,b,p) << endl;
}
return 0;
}
求组合数4 分解质因数+高精
原题链接:https://www.acwing.com/problem/content/890/
思路
这个题没有要求mod一个数
a和b可以取到5000,结果会很大。
可以将公式\(C_{a}^{b} = \frac{a!}{(a-b)! * b!}\) 的分子分母分解质因数 , 最后让质因子们相乘就可以得到答案.
质因子相乘需要使用高精度乘法
如何求得\(a!\)的质因子们的个数
比如质因子p让 a 分别除以 p^1、p^2、p^3...p^n(直到p^n大于a)
就可以得到a!里面有几个p
例如 12! = 12*11*10*9*8*7*6*5*4*3*2*1
除以 2^1 2^2 2^3就可以得到12阶乘里面有几个2
代码
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
const int N = 5010;
int primes[N],cnt;
bool st[N];
int sum[N];
void get_primes(int n)
{
for(int i = 2; i <= n; i ++)
{
if(!st[i]) primes[cnt ++] = i;
for(int j = 0; i * primes[j] <= n ; j ++)
{
st[primes[j] * i] = true;
if(i % primes[j] == 0) break;
}
}
}
// 求x!中有多少个p
int get(int x, int p)
{
int res = 0;
while(x)
{
res += x / p;
x /= p;
}
return res;
}
// 高精度乘法
vector<int> mul(vector<int> a,int b)
{
vector<int> c;
int t = 0;
for(int i = 0; i < a.size(); i ++)
{
t += a[i] * b;
c.push_back(t % 10);
t /= 10;
}
while(t)
{
c.push_back(t % 10);
t /= 10;
}
return c;
}
int main()
{
int a,b;
cin >> a >> b;
// 素数筛
get_primes(a);
// 求a!、(a-b)!、b!里有多少个素数质因子=>求Cab中有多少个p
for(int i = 0; i < cnt; i ++)
{
int p = primes[i];
sum[i] = get(a,p) - get(a-b,p) - get(b,p);
}
vector<int> res;
res.push_back(1);
// 高精度乘法求结果
for(int i = 0; i < cnt; i ++)
{
for(int j = 0; j < sum[i]; j ++)
{
res = mul(res,primes[i]);
}
}
for(int i = res.size() - 1; i >= 0; i --) cout << res[i];
return 0;
}