求组合数I II III IV

求组合数

求组合数1 递推 $ O(n^2) $

原题链接:https://www.acwing.com/problem/content/887/

思路

数据范围为2000,可以在\(n^2\)以内解决问题,就直接使用下面的递推即可

已知公式

\[C_{a}^{b} = C_{a-1}^{b} + C_{a-1}^{b-1} \]

就用此公式递推求即可

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)\)

已知有公式

\[C_{a}^{b} = \frac{a!}{(a-b)!b!} \]

可以预处理出来阶乘,但是最后计算的时候不能直接除以相应的阶乘
因为:(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

\[C_{a}^{b} \equiv C_{a\;mod\;p}^{b\;mod\;p} * C_{a/p}^{b/p} \]

使用卢卡斯定理,可以将时间复杂度变成\(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;
}
posted @ 2022-09-13 11:20  r涤生  阅读(21)  评论(0编辑  收藏  举报