排列组合(低精度*高精度,卢卡斯定理,阶乘数某个质因数的幂次

885. 求组合数 I(递推)

给定 n 组询问,每组询问给定两个整数 a,b,请你输出 C a b m o d ( 1 0 9 + 7 ) C_a^b mod (10^9+7) Cabmod(109+7) 的值。

输入格式
第一行包含整数 n。

接下来 n 行,每行包含一组 a 和 b。

输出格式
共 n 行,每行输出一个询问的解。

数据范围
1 ≤ n ≤ 10000 1≤n≤10000 1n10000,
1 ≤ b ≤ a ≤ 2000 1≤b≤a≤2000 1ba2000

输入样例:

3
3 1
5 3
2 2

输出样例:
要考虑时间复杂度的限制,观察数据范围
10000个组合数,组合数上下标a,b范围是2000,利用公式
c ( a , b ) = a ! / ( b ! ∗ ( a − b ) ! ) c(a,b)=a!/(b!*(a-b)!) c(a,b)=a!/(b!(ab)!),每计算一个组合数时间复杂度就是a,b的范围2000逐一计算组合数,
10000 ∗ 2000 = 2 ∗ 1 0 8 10000*2000=2*10^8 100002000=2108
相比于预处理,算出a,b任一上下标组合的组合数,复杂度不过 2000 ∗ 2000 = 4 ∗ 1 0 6 2000*2000=4*10^6 20002000=4106
自然选后者

3
10
1
#include <iostream>
using namespace std;
const int N=2005;
const int mod=1e9+7;
int c[N][N];
void init(){//求出$C_i^j$ 
	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(){
	init();
	int n;
	cin>>n;
	int a,b;
	while(n--){
		cin>>a>>b;
		cout<<c[a][b]<<endl;
	}
    return 0;
}

886. 求组合数 Ⅱ(初始化fact[])

数据范围换为
1 ≤ n ≤ 10000 1≤n≤10000 1n10000,
1 ≤ b ≤ a ≤ 1 0 5 1≤b≤a≤10^5 1ba105
这时 n ∗ a < a ∗ a n*a<a*a na<aa,就应该给一对a,b,求这个组合数了
而不再是先递推保存好
对于getC(int m,int n)
可以是

return fact[n]*quickpow(fact[n-m],mod-2)%mod*quickpow(fact[m],mod-2)%mod;

也可以先递推处infact[N]数组

return fact[n]*infact[n-m]%mod*infact[m]%mod;

只需要initfact初始化fact数组时同时初始化infact数组

void initfact(){
	fact[0]=infact[0]=1;
	for(int i=1;i<N;i++){
		fact[i]=i*fact[i-1]	%mod;
		infact[i]=infact[i-1]*quickpow(i,mod-2)%mod;
	} 
}

1 x ! \displaystyle \frac{1}{x!} x!1% m o d mod mod== 1 ( x − 1 ) ! \displaystyle \frac{1}{(x-1)!} (x1)!1* i n f ( x ) inf(x) inf(x) % m o d mod mod
除以x相当于乘上x的逆元

#include <iostream>
using namespace std;
typedef long long ll;
const int N=100005,mod=1e9+7;
ll fact[N];//fact[i]求i得阶乘  注意了N是组合数下标的范围,不是样例数 
void initfact(){
	fact[0]=1;
	for(int i=1;i<N;i++){
		fact[i]=i*fact[i-1]	%mod;
	} 
}
ll quickpow(ll b,ll e){
	b%=mod;
	ll res=1;
	while(e){
		if(e&1)res=(b*res)%mod;
		b=(b*b)%mod;
		e>>=1; 
	}
	return res;
}
ll getC(int n,int m){//求C_n^m 
	return fact[n]*quickpow(fact[n-m],mod-2)%mod*quickpow(fact[m],mod-2)%mod;
}//两个阶乘不会爆ll,这三个相乘会爆,一定要在两个乘完后%一下 
int main(){
	initfact();
	int n;
	cin>>n;
	int a,b;
	while(n--){
		cin>>a>>b;
		cout<<getC(a,b)<<endl; 
	}
    return 0;
}

887. 求组合数Ⅲ(卢卡斯定理,模p在 1 0 5 10^5 105左右,n、m不爆longlong就行1≤n≤m≤ 10 ^ 18, 时间复杂度 O ( p ∗ l o g p m ∗ l o g 2 p O(p*log_pm*log_2p O(plogpmlog2p

给定 n 组询问,每组询问给定三个整数 a,b,p,其中 p 是质数,请你输出 C b a   m o d   p C_b^a\ mod\ p Cba mod p的值。

输入格式
第一行包含整数 n。

接下来 n 行,每行包含一组 a,b,p。

输出格式
共 n 行,每行输出一个询问的解。

数据范围
1 ≤ n ≤ 20 , 1 ≤ b ≤ a ≤ 1 0 1 8 , 1 ≤ p ≤ 1 0 5 , 1≤n≤20, \quad 1≤b≤a≤ 10 ^ 18, \quad 1≤p≤10^5, 1n20,1ba1018,1p105,

输入样例:
3
5 3 7
3 1 5
6 4 13
输出样例:
3
3
2

#include <iostream>
using namespace std;
typedef long long ll;
ll n,m,p;
void initfac(){
	fac[0]=1;
	for(int i=1;i<N;i++){
		fac[i]=i*fac[i-1]%p;
	}
}
ll quickpow(ll b,ll e){
	b%=p;
	ll res=1;
	while(e){
		if(e&1)res=res*b%p;
		b=(b*b)%p;
		e>>=1;
	}
	return res%p;
}
ll C(ll a,ll b){
	if(b>a)return 0;
	if(b>a-b)b=a-b;//C_a^b和C_a^(a-b) 
	ll x,y;//分别代表分子分母
	x=y=1;//不初始化就等着乘0把 
	for(int i=0;i<b;i++){//C_5^2
		x=x*(a-i)%p;
		y=y*(i+1)%p;
	} 
	return x*quickpow(y,p-2)%p;
}
ll lucas(ll a,ll b){
	if(b==0)return 1;
	return C(a%p,b%p)*lucas(a/p,b/p)%p;
}
int main(){

	int t;
	cin>>t;
	while(t--){
		cin>>n>>m>>p;
		cout<<lucas(n,m)<<endl;
	}
    return 0;
}

卢卡斯定理模板

如果没有先给出mod,是不能预先初始化阶乘矩阵的,也不能递推(1e^5地平方必然超时)
必须这样实实在在地求

ll C(int a,int b){
	if(b>a)return 0;
	if(b>a-b)b=a-b;//C_a^b和C_a^(a-b)对求预处理fac无影响,这里有 
	ll x,y;//分别代表分子分母
	x=y=1;//不初始化就等着乘0把 
	for(int i=0;i<b;i++){//C_5^2
		x=x*(a-i)%p;
		y=y*(i+1)%p;
	} 
	return x*quickpow(y,p-2)%p;
}

P3807 【模板】卢卡斯定理/Lucas 定理

#include <iostream>
using namespace std;
const int N=1e5+2;
typedef long long ll;
ll fac[N];
int n,m,p;

void initfac(){
	fac[0]=1;
	for(int i=1;i<N;i++){
		fac[i]=i*fac[i-1]%p;
	}
}
ll quickpow(ll b,ll e){
	b%=p;
	ll res=1;
	while(e){
		if(e&1)res=res*b%p;
		b=(b*b)%p;
		e>>=1;
	}
	return res%p;
}
ll getC(int a,int b){
	if(b>a)return 0;
//	if(b>a-b)b=a-b;//其实算C_a^b和C_a^(a-b)一样的啦,没必要换 
	return fac[a]*quickpow(fac[b],p-2)%p*quickpow(fac[a-b],p-2)%p;
}
ll C(int a,int b){
	if(b>a)return 0;
	if(b>a-b)b=a-b;//C_a^b和C_a^(a-b) 
	ll x,y;//分别代表分子分母
	x=y=1;//不初始化就等着乘0把 
	for(int i=0;i<b;i++){//C_5^2
		x=x*(a-i)%p;
		y=y*(i+1)%p;
	} 
	return x*quickpow(y,p-2)%p;
}
ll lucas(int a,int b){
	if(b==0)return 1;
	return C(a%p,b%p)*lucas(a/p,b/p)%p;
}
int main(){

	int t;
	cin>>t;
	while(t--){
		cin>>n>>m>>p;
		n+=m;
		cout<<lucas(n,m)<<endl;
	}
    return 0;
}

感谢博主

888. 求组合数 Ⅳ(结果不取模,质因数的幂次高精度相乘,一个(阶乘)数总能分解成若干个质因数的幂次的乘积,求某个质因数的幂次几何)

输入 a,b,求 C b a C_b^a Cba 的值。

注意结果可能很大,需要使用高精度计算。

输入格式
共一行,包含两个整数 a 和 b。

输出格式
共一行,输出 C b a C_b^a Cba 的值。

数据范围
1 ≤ b ≤ a ≤ 5000 1≤b≤a≤5000 1ba5000
输入样例:
5 3
输出样例:
10

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
typedef long long ll;
const int N=5005;
int vis[N];
int prime[N];
int cnt=0;
void getPrime(int n){//欧拉筛法 
	vis[0]=vis[1]=1;
	for(int i=2;i<=n;i++){
		if(!vis[i])prime[cnt++]=i;
		for(int j=0;j<cnt&&prime[j]<=n/i;j++){//i*prime[j]<n,在右边除以i不会溢出 
			vis[i*prime[j]]=1;
			if(i%prime[j]==0)break;
		} 
	} 
}
int getExpon(int n,int p){ //
//n!分解成质因数的幂次相乘的形式,其中质因数的幂次
 
//求一个数的阶乘 这个数 分解成质因数的幂次相乘的形式
//对于质因子p的幂次是多少,阶乘是若干个数相乘,只要把其中
//是p的倍数的数 、是p^2的倍数的数、是p^3的倍数的数…
//把这些倍数数值相加,不会重复的,虽然 是p^3的倍数的数也是是p^2的倍数
//在 是p^3的倍数的数这儿幂次要算上3,但在p、p^2那儿各算了一次 
//在 是p^x的倍数的数这儿幂次要算上x,但在p、p^2…p^(x-1)那儿
//各算一次,共算了x-1次 
	int res=0;
	while(n){
		res+=n/p;//是p的倍数的数
		n/=p;//1~n/p 有x个是p的倍数的数 ,则1~n有x个是p^2的倍数的数 
	} 
	return res;
} 
int e[N];//e[i]记录getPrime[i]这个质因数的幂次 
vector<int> mul(vector<int> a,int b){//a数组表示的成熟就是倒置的 
	vector<int> c;
	int temp=0;
	for(int i=0;i<a.size();i++){
		temp+=a[i]*b;
		c.push_back(temp%10);
		temp/=10;
	}
	while(temp){//处理剩余0 
		c.push_back(temp%10);
		temp/=10;
	}
	while(c.size()>1&&c.back()==0)c.pop_back();//去先导0 
	return c;
}

int main(){
	int n,m;
	cin>>n>>m;
	getPrime(n);
	for(int i=0;i<cnt;i++){
		int p=prime[i];
		e[i]=getExpon(n,p)-getExpon(m,p)-getExpon(n-m,p);
	}
	//求Prime[i] 的e[i]次方 
	vector<int> res;
	res.push_back(1);
	for(int i=0;i<cnt;i++){//低精度连乘出高精度(高精度*低精度) 
		for(int j=0;j<e[i];j++){//Prime[i] 连乘e[i]次 
			res=mul(res,prime[i]);
		} 
	} 
	reverse(res.begin(),res.end());
	for(int i=0;i<res.size();i++){
		cout<<res[i];
	} 
    return 0;
}

889. 满足条件的01序列

给定 n 个 0 和 n 个 1,它们将按照某种顺序排成长度为 2n 的序列,求它们能排列成的所有序列中,能够满足任意前缀序列中 0 的个数都不少于 1 的个数的序列有多少个。

输出的答案对 1 0 9 + 7 10^9+7 109+7 取模。

输入格式
共一行,包含整数 n。

输出格式
共一行,包含一个整数,表示答案。

数据范围
1 ≤ n ≤ 1 0 5 1≤n≤10^5 1n105
输入样例:
3
输出样例:
5
quickpow两个参数一定记得全部用longlong,否则会出现结果为0
全用longlong啦,一了百了

#include <iostream>
using namespace std;
typedef long long ll;
const int mod=1e9+7;
const int N=1e5+5;
ll quickpow(ll b,ll e){
	b%=mod;
	ll res=1;
	while(e){
		if(e&1)res=res*b%mod;
		b=b*b%mod;
		e>>=1;
	}
	return res;
}
ll C(int n,int m){
	if(m>n)return 0;
	if(n-m<m)m=n-m;
	ll x,y;//分子分母
	x=y=1;
	for(int i=0;i<m;i++){
		x=x*(n-i)%mod;
		y=y*(i+1)%mod;
	} 
	return x*quickpow(y,mod-2)%mod;
}
int main(){//C_2n^n-C_2n^(n-1)=C_2n^n/(n+1)
//C_2n^n=(2n)!/n!/n!
//C_5^2=5*4/2*1 
	int n;
	cin>>n;
	cout<<C(2*n,n)*quickpow(n+1,mod-2)%mod;
    return 0;
}

伯努利错装信封问题

递归解决

f ( n ) = ( n − 1 ) ∗ ( f ( n − 1 ) + f ( n − 2 ) ) f(n)=(n-1)*(f(n-1)+f(n-2)) f(n)=(n1)(f(n1)+f(n2))

错排:n封信放入n个信封,要求全部放错,共有多少种放法,记n个元素的错排总数为f(n)

假设有n封信,第一封信可放在(2-n)的任一个信封里,共n-1种放法,设第一封信放在了第k个信封里,若此时第k封信放在了第1个信封里,则只要将剩下的n-2错排,即f(n-2),若第k封信没有放在了第1个信封里,可将第1封信的位置看成是“第k个位置”,即将n-1封信错排,即为f(n-1)

从第一封信开始匹配,以信封数量为规模 有两种情况
1、假设 信1和信封x搭配 ,信x和信封1搭配 ,解决问题规模缩小到n-2
当然得注意,x只有n-1种选择
2、假设 信1和信封x搭配 ,信x不和信封1搭配 ,解决问题规模缩小到n-1
x还是有n-1种选择

0%错误,why?没用long long呀

#include <iostream>
using namespace std;
int fac(int n){
	if(n==0||n==1)return 0;
	if(n==2)return 1;
	return (n-1)*(fac(n-1)+fac(n-2));
}
int main(){
	int n;
	while(cin>>n){
		cout<<fac(n)<<endl;
	} 
    return 0;
}

#include <iostream>
using namespace std;
typedef long long ll;
ll fac(int n){
	if(n==0||n==1)return 0;
	if(n==2)return 1;
	else return (n-1)*(fac(n-1)+fac(n-2));
}
int main(){
	int n;
	while(cin>>n){
		cout<<fac(n)<<endl;
	} 
    return 0;
}
#include<bits/stdc++.h>
using namespace std;
const int N=22;
long long f[N];
int main()
{
	f[1]=0,f[2]=1,f[3]=2;
	for(int i=4;i<=20;i++)
		f[i]=(i-1)*(f[i-2]+f[i-1]);
	int n;
	while(cin>>n) {
		cout<<f[n]<<endl; 	
	}
	return 0; 
}

全错位排列定理

容斥原理——>全错位排列
全错位排列之分布列、期望
在这里插入图片描述

#include <iostream> 
using namespace std;
typedef long long ll;
ll fac[25];
void init(){
	fac[1]=1;
	for(int i=2;i<=20;i++){
		fac[i]=i*fac[i-1];
	}
}
int main(){
	init();//不要忘记init呀 
	int n;
	while(cin>>n){
		ll s=fac[n];
		ll  sum=0;
		for(int i=2;i<=n;i++){//n!(1-1/1! +1/2! -1/3!…… 
			if(i%2==0)sum+=s/fac[i];
			else sum-=s/fac[i];
		}
		cout<<sum<<endl;
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值