求组合数的各种方法 (Lacus等) (理论待更新,目前只有模板)

理论待更新,今天更完

Lacus定理

注意!如果m%p>n%p,那么C(n%p,m%p,p)=0 也就是下标小于上标 结果也就是0了 这一点务必要注意 

#include<cstdio>//求单个组合数 卢卡斯定理 n<=1e18 m<=1e18 p为素数但比较小! p<=1e4 注意!如果m%p>n%p,那么C(n%p,m%p,p)=0 也就是下标小于上标 结果也就是0了 这一点务必要注意 
#include<string.h>//易错:计算时记得换long long否则溢出会得到负数!
#include<vector>
using namespace std;
typedef long long ll;
const int p=1e5+3;//注意,1e5+7不是质数,他是97*1031的结果! 
inline ll read(){//快读  若在与同一行有string的情况下使用,把下面的readS也写上 如果变long long,记得把s前的int也变了 
	char ch=getchar();
	ll s=0,w=1;//long long s=0,w=1;
	while(ch<'0' || ch>'9'){
		if(ch=='-')	w*=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9'){//不要写成|| ! 
		s=ch-'0'+(s<<3)+(s<<1);//最大易错点!s=而不是s+= 
		ch=getchar();//不要忘记! 
	}//如果这个数字后面有回车,这个循环会接收回车,所以不需要在额外来个getchar() 
	return s*w;
}
ll exgcd(ll a,ll b,ll &x,ll &y){//扩展欧几里得算法 ax+by=c 求解a,b 
    if(!b){
        x=1,y=0;
        return a;
    }
    ll ans=exgcd(b,a%b,x,y);
    ll temp=x;
    x=y;
    y=temp-a/b*y;
    return ans;
}
ll inv(ll a,ll p){//求逆元 ax1(mod p)=>a*x+p*y=1 这里我们求x 要求a与p互质,即gcd(a,p)=1 如果不存在返回-1 
	ll x,y,gcd;
	gcd=exgcd(a,p,x,y);
	return (x%p+p)%p; //x%p+p确保逆元为正数  
} 
ll C(ll n, ll m) {//求组合数
	ll ans=1,numP=0;//ans存放计算结果,numP统计分子中的p比分母中的p多几个
	for(ll i = 1; i <= m; i++) {//对分子每个数进行质因子分解
		ll temp= n - m + i;//分子
		while(temp%p==0) {
			numP++;
			temp/=p;
		}
		ans=ans*temp%p;//乘以分子中除了p以外的部分
		temp=i;//分母 
		while(temp%p==0) {
			numP--;
			temp/=p;
		}
		ans=ans*inv(temp,p)%p;//除以分母中除了p以外的部分
	}
	if(numP>0) return 0; //分子中p的个数多于分母,直接返回0
	else return ans;//分子中p的个数等于分母,返回计算的结果
}
ll Lucas(ll n,ll m){
	if(m==0) return 1;
	if(m%p > n%p)	return 0;//注意!如果m%p>n%p,那么C(n%p,m%p,p)=0 也就是下标小于上标 结果也就是0了 这一点务必要注意 
	return C(n%p, m%p)*Lucas(n/p,m/p)%p;
}
int main() {
	ll t=Lucas(1e17+48,1e8+6);//18716 19718 5393
	printf("%lld\n",t);
	return 0;
}

模板题https://www.luogu.com.cn/problem/P3807

杨辉三角完全打表

#include<cstdio>//杨辉三角全打表,1e4范围较快,受栈空间大小和时间影响 
int res [10050] [10050] = {};
const int N=10000,p=1e9+7;
int C(int n, int m) {
	if(m == 0 || m == n) return 1; //C(n, 0) = C(n, n) = 1
	if (res [n] [m] != 0) return res [n] [m]; //已经有值
	return res[n] [m] = (C(n - 1, m) + C(n - 1, m - 1))%p; //赋值并返回
}
void calC() {
	for (int i = 0; i <= N; i++) {
		res [i] [0] = res [i] [i] = 1; //初始化边界
	}
	for (int i = 2; i <= N; i++) {//i为下标j为上标
		for(int j = 0; j <= i / 2; j++) {
			res[i] [j] = (res[i - 1] [j] + res[i-1] [j-1])%p; //递推计算C(i, j)
			res[i] [i -j] = res[i] [j]; //C(i, i - j) = C(i, j)
		}
	}
}
int main(){
	calC();
	return 0;
}

杨辉三角不完全打表

#include<cstdio>//利用C(n,m)=C(n,m-1)+C(n-1,m-1)递推计算 适用于n,m<=1e4的情况 对p的大小和素性没有限制 (栈空间数组大小限制和时间限制)
const int p=1e9+7; 
int res [10010] [10010] = {};
int C(int n, int m) {//递归 
	if(m == 0 || m == n) return 1; //C(n, 0) = C(n, n) = 1
	if (res [n] [m] != 0) return res [n] [m]; //已经有值
	return res[n] [m] = (C(n - 1, m) + C(n - 1, m - 1))%p; //赋值并返回
}
int main(){
	int t=C(9010,3329);
	printf("%d",t);
}

质因子分解法 

#include<string.h>//求单个C(n,m)质因子分解法 时间复杂度klogn  k为不超过n的质数个数 n,m<=1e6 对p的大小和素性无限制 
#include<cstdio>//易错:计算时记得换long long否则溢出会得到负数! 
#include<vector>
using namespace std;
typedef long long ll;
const int N=1e6+10,p=1e9+7; //N为素数表大小 
bool isnp[N]; //isnp[i]=0则i为素数,否则为合数
vector<int> prime; // 质数表
void init() {
	for (int i = 2; i < N; i++) {
		if (!isnp[i])
			prime.push_back(i);
		for (int p=0; p<prime.size(); p++) {
			if (i*prime[p] >= N)	break;
			isnp[i*prime[p]]= 1;
			if (i % prime[p] == 0)	break;
		}
	}
}
int cal(int n, int p) {//求n!的质因子p的指数
	int ans = 0;
	while (n) {
		ans+=n/p;
		n/=p;
	}
	return ans;
}
ll binaryPow(ll a, ll b, ll m) {//快速幂
	if (b == 0) return 1;
	if(b % 2 == 1) return a* binaryPow(a, b - 1, m) % m;
	else {
		ll mul = binaryPow(a, b / 2, m);
		return mul* mul % m;
	}
}
ll C(ll n, ll m) {//计算C(n,m)%p
	ll ans = 1;
	for(ll i = 0; prime[i] <= n; i++) {
		ll c = cal(n, prime[i]) - cal(m, prime[i]) - cal(n - m, prime[i]);
		ans= ans* binaryPow(prime[i], c, p) % p;
	}
	return ans;
}
int main() {
	init();
	ll t=C(1e5+4513,1e5+7);
	printf("%lld",t);
	return 0;
}

质因子分解法2

#include<cstdio>//求单个组合数 定义式变形法 n<=1e9 m<=1e6 p为素数 p可以小于m  
#include<string.h>//易错:计算时记得换long long否则溢出会得到负数!
#include<vector>
using namespace std;
typedef long long ll;
const int p=1e9+7;
ll exgcd(ll a,ll b,ll &x,ll &y){//扩展欧几里得算法 ax+by=c 求解a,b 
    if(!b){
        x=1,y=0;
        return a;
    }
    ll ans=exgcd(b,a%b,x,y);
    ll temp=x;
    x=y;
    y=temp-a/b*y;
    return ans;
}
ll inv(ll a,ll p){//求逆元 ax1(mod p)=>a*x+p*y=1 这里我们求x 要求a与p互质,即gcd(a,p)=1 如果不存在返回-1 
	ll x,y,gcd;
	gcd=exgcd(a,p,x,y);
	return (x%p+p)%p; //x%p+p确保逆元为正数  
} 
ll C(ll n, ll m) {//求组合数
	ll ans=1,numP=0;//ans存放计算结果,numP统计分子中的p比分母中的p多几个
	for(ll i = 1; i <= m; i++) {//对分子每个数进行质因子分解
		ll temp= n - m + i;//分子
		while(temp%p==0) {
			numP++;
			temp/=p;
		}
		ans=ans*temp%p;//乘以分子中除了p以外的部分
		temp=i;//分母 
		while(temp%p==0) {
			numP--;
			temp/=p;
		}
		ans=ans*inv(temp,p)%p;//除以分母中除了p以外的部分
	}
	if(numP>0) return 0; //分子中p的个数多于分母,直接返回0
	else return ans;//分子中p的个数等于分母,返回计算的结果
}
int main() {
	ll t=C(196282623,1002422);
	printf("%lld\n",t);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值