求组合数 I:
注意:1~1w组数据
cpp
#include<iostream>
using namespace std;
const int mod = 1e9+7;
long long f[2010][2010];
int main()
{
int n;
scanf("%d",&n);
//预处理
for(int i=0;i<=2000;i++)
{
for(int j=0;j<=i;j++)
{
if(!j) f[i][j]=1;
else f[i][j]=(f[i-1][j-1]+f[i-1][j])%mod;//如上图公式所得
}
}
while(n--)
{
int a,b;
scanf("%d%d",&a,&b);
printf("%lld\n",f[a][b]);
}
}
求组合数 II:
注意:1~10w组数据
cpp
#include<iostream>
using namespace std;
const int mod=1e9+7,N=1e5+10;
typedef long long LL;
long long fac[N],infac[N];//fac[N]阶乘mod p的值,infac[N]阶乘的逆元mod p的值
int quick_pow(int a, int k, int p)//快速幂求逆元
{
int res = 1;
while (k)//对k进行二进制化,从低位到高位
{
//如果k的二进制表示的第0位为1,则乘上当前的a
if (k & 1) res = (LL)res * a % p;
//k右移一位
k >>= 1;
//更新a
a = (LL)a * a % p;
}
return res;
//a:4 b:3 p:9
//二进制b 11 1
//res 1*4%9=4 4*7%9=1
//b 1 0
//a 4*4%9=7 16*16%9=112
//因为:2^0=1,2^1=2,2^2=4,2^3=8...
//根据公式:b=3,所以2^0+2^1=b,与这个指数上面的b算的一样
//所以(4^2^0%9*4^2^1%9)%9=(4*7)%9=28%9=1,这个就是上面的res
}
int main()
{
int n;
fac[0]=infac[0]=1;
for(int i=1;i<=1e5;i++)
{
fac[i]=fac[i-1]*i%mod;//i的阶乘就是上一个阶乘*i
infac[i]=(LL)infac[i - 1] * quick_pow(i,mod-2,mod)%mod;//i的逆元阶乘就是上一个数的逆元阶乘*i的逆元
}
scanf("%d",&n);
while(n--)
{
int a,b;
scanf("%d%d",&a,&b);
printf("%lld\n",(LL)fac[a] * infac[b] % mod * infac[a - b] % mod);//由上图公式得
}
}
求组合数 III:
注意:1~20w组数据
cpp
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
int qmi(int a,int k,int p)//快速幂求逆元
{
int res = 1;
while(k)
{
//如果k的二进制表示的第0位为1,则乘上当前的a
if(k&1)res = (LL)res*a%p;
//k右移一位
k>>=1;
//更新a
a = (LL)a*a%p;
}
return res;
}
int C(int a,int b,int p)//自变量类型int
{
if(b>a)return 0;//边界条件
int res = 1;
// a!/(b!*(a-b)!) = (a-b+1)*...*a / b! 分子有b项,这是组合公式
for(int i=1,j=a;i<=b;i++,j--)//图二所示公式,递推的每次乘a然后除以b, 因为从a到a−b+1, 所以就是乘b次
{
res = (LL)res*j%p;//乘a,此时a就是j
res = (LL)res*qmi(i,p-2,p)%p;//除以b,就是*b的逆元,此时i就是b
}
return res;
}
//对公式敲
int lucas(LL a,LL b,int p)
{
if(a<p && b<p)return C(a,b,p);//lucas递归终点是C()
return (LL)lucas(a/p,b/p,p)*C(a%p,b%p,p)%p;//这是lucas公式,也是最后需要的答案,a%p后肯定是<p的,所以可以用C(),但a/p后不一定<p 所以用lucas继续递归
}
int main()
{
int n;
scanf("%d",&n);
while(n--)
{
LL a,b;
int p;
scanf("%lld%lld%d",&a,&b,&p);
printf("%d\n",lucas(a,b,p));
}
return 0;
}
求组合数 IV:
注意:答案可能很大,需要使用高精度计算
cpp
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int N=5010;
int primes[N],cnt;//primes[N]用来存放质数,cnt代表质数的个数
int sum[N];//每个质数的次方
bool st[N];//st[i], i为质数则为false否则为true,默认全是质数
void get_primes(int n)//线性筛质数,即筛出每个p
{
for(int i=2;i<=n;i++)//i是某个数的意思,要找2~n的所有数是否为质数
{
if(!st[i])primes[cnt++]=i;//如果i该数是质数,则放进数组,cnt++代表质数个数加一
for(int j=0;primes[j]<=n/i;j++)//把小于n的合数都筛了
{
st[primes[j]*i]=true;//i无论是质数还是合数,它的质数倍数都可以赋true
//例:i=2时,找到i的最小质因子为2,所以2*2=4被赋true
// i=3时,找到i的最小质因子为3,所以3*3=9被赋true
// i=4时,前面已经找到4为合数所以不会让上面的if出错
//像这样,不会让任何i和合数的情况下未被赋true,而导致上面的if出错
//当primes[j]是i的最小质因子,说明primes[j]一定是i的倍数primes[j]*i的最小质因子
//则说明对于i的倍数primes[j+k]*i的最小质因子也是primes[j],而非primes[j+k]
//后面的任何i的倍数都不用看了,直接break,让i++找下一个数是否为质数
//当i%primes[j]!=0时,说明此时遍历到的primes[j]不是i的质因子,所以primes[j]*i的最小质因子不是primes[j]
//可以继续找primes[j+k]的i倍是否为最小质因子
if(i%primes[j]==0)break;
}
}
}
//计算n的阶乘中<=n的p的指数最大为多少,如2,2^2,2^3...,算最大为几次方
int get(int n,int p)
{
int res =0;
while(n)
{
res+=n/p;
n/=p;
}
return res;
}
//高精度乘法把所有质因子乘起来
vector<int> mul(vector<int> a, int b)
{
vector<int> c;
int t = 0;
for (int i = 0; i < a.size() || t; i++)//只要有进位或者A没读完,就继续
{
if (i < a.size())t += a[i] * b;//乘法,A是低位在前,所以先算的低位
c.push_back(t % 10);//把余数放进来
t /= 10;//给下一位的进位
}
return c;
}
int main()
{
int a,b;
scanf("%d%d",&a,&b);
get_primes(a);//找到1~a的所有质数
for(int i=0;i<cnt;i++)//把所有的质数算出a的阶乘中该质数的最大次方为多少
{
int p = primes[i];
//当前i这个数包含的p的个数=a的阶乘的p的个数-(a-b)的阶乘的p的个数-b的阶乘的p的个数
//用分子里面 p 的次方减去分母里面 p 的次方。这里的计算组合数的公式为a!/(b!*(a-b)!),因此用 a 里面 p 的次方减去 b 里面 p 的次方和 (a-b) 里面 p 的次方。
sum[i] = get(a,p)-get(a-b,p)-get(b,p);//是a-b不是b-a
}
vector<int> res;
res.push_back(1);
for (int i = 0; i < cnt; i ++ )//每个质数
for (int j = 0; j < sum[i]; j ++ )//primes[i]的次方,即每个质数的次方
res = mul(res, primes[i]);//高精度乘法
for (int i = res.size() - 1; i >= 0; i -- ) printf("%d", res[i]);
printf("\n");
return 0;
}
满足条件的01序列:
将 01 序列置于坐标系中,起点定于原点。若 0 表示向右走,1 表示向上走,那么任何前缀中 0 的个数不少于 1的个数就转化为,路径上的任意一点,横坐标大于等于纵坐标。题目所求即为这样的合法路径数量。
答案如图,即卡特兰数。
cpp
#include <iostream>
using namespace std;
typedef long long LL;
const int N = 200010, mod = 1e9 + 7;
int n;
int fact[N], infact[N];//fac[N]阶乘mod p的值,infac[N]阶乘的逆元mod p的值
int ksm(int a, int k,int mod) //快速幂求逆元
{
int res = 1;
while (k) //对k进行二进制化,从低位到高位
{
if (k & 1) res = (LL)res * a % mod;//如果k的二进制表示的第0位为1,则乘上当前的a
k >>= 1;//k右移一位
a = (LL)a * a % mod;//更新a
}
return res;
//a:4 b:3 p:9
//二进制b 11 1
//res 1*4%9=4 4*7%9=1
//b 1 0
//a 4*4%9=7 16*16%9=112
//因为:2^0=1,2^1=2,2^2=4,2^3=8...
//根据公式:b=3,所以2^0+2^1=b,与这个指数上面的b算的一样
//所以(4^2^0%9*4^2^1%9)%9=(4*7)%9=28%9=1,这个就是上面的res
}
int main()
{
fact[0] = infact[0] = 1;
for (int i = 1; i < N; i++)
{
fact[i] = (LL)fact[i - 1] * i % mod;//i的阶乘就是上一个阶乘*i
infact[i] = (LL)infact[i - 1] * ksm(i, mod - 2,mod) % mod;//i的逆元阶乘就是上一个数的逆元阶乘*i的逆元
}
scanf("%d",&n);
//由卡特兰公式:2n!/n!/n!/n+1,这里的除以n+1可以写成乘以n+1的逆元
int res = (LL)fact[2 * n] * infact[n] % mod * infact[n] % mod * ksm(n + 1, mod - 2,mod) % mod;
printf("%d\n",res);
return 0;
}