一中OJ #3167 排列组合数计算[2] | 高级数论 组合计数 | 解题报告

2 篇文章 0 订阅
1 篇文章 0 订阅

一中OJ | #3167 排列组合数计算[2]

时限 1000MS/Case 内存 64MB/Case





题目描述

给出 m 个元素的集合,从其中选择 n 个元素的排列数为A(n,m)、组合数为C(n,m)。请你用递推算法计算这两个数。

输入格式

两个正整数 m 和 n 。

输出格式

第一行输出A(n,m);
第二行输出C(n,m);
这两个数可能很大,请把他们的结果模100000007后输出。

样例输入

5 2

样例输出

20

10

数据范围

1<=n<=m<=10^6

----------------------------------------------------------

题目分析

计算组合数的办法列如下:

1.组合数公式


最简单,对于m,n在20以内好使,在这里就不提了

2.组合数递推式(模拟杨辉三角形)

C(n,m)=C(n,n-m)=C(n-1,m-1)+C(n-1,m)

用递归是撑不了多久的,填表稍微好些,大概能算到10^3左右;优化后可以到10^4级别

10^4的递推代码:

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <set>
#include <queue>
#include <stack>
#include <vector>
#include <map>
#define hashsize 1000003
#define inf 0x7f7f7f7f
#define mo 100000007
using namespace std;
int n,m;
long long arrangement=1;
long long combination[500005]={0,1,2,1};
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=n;i>=n-m+1;i--) arrangement=arrangement*i%mo;
	if(m>n-m) m=n-m;
	cout<<arrangement<<endl;
	for(int i=3;i<=n;i++)
	{
		for(int j=min(m+1,i+1);j>=max(1,m+1-n+i);j--)
		{
			combination[j]=(combination[j]+combination[j-1])%mo;
		}
	}
	
	cout<<combination[m+1];
	return 0;
}
3.Lucas定理 && 4.乘法逆元

这个在其他地方有更详细的介绍,我也说不清楚【摊手】

传送门:

[组合数]求组合数的几种方法总结 - CSDN Blog @ 穆林幕

[百度百科]乘法逆元

[百度百科]Lucas定理

5.[重头戏]整数唯一分解定理(分解质因数)

[百度百科]唯一分解定理

任何大于 1 的自然数 n,都可唯一分解成有限个质因数乘积的形式:


然后我们已知组合数公式为

因为c(m,n)一定是整数

又因为m>n>(m-n),m!的质因子所有一定<m(因为m可分解成<=m的质因子们,那么m-1可以分更小,m-2...可以分更更小),所以可以先算出m以内的所有质数

然后获得m!,n!,(m-n)!的唯一质因数分解式,再将m!/(n!(m-n)!)中的除法当做质因数分解式指数的减法,最后算


(其中p<=m且p为质数,e[i]为做完指数减法后的指数)即可

----------------------------------------------------------

附:在O(n)内求出n!的唯一分解的方法

设P为质数,n∈正整数,则质数P在N!的质因数分解式中的幂次为{N/P^1+N/P^2+N/P^3+...+N/P^X | P^X <= N}

证明:假设N=10,P=2(此处'/'为整除)

◆1*2*3*4*5*6*7*8*9*10 中,含因子 2 的数为:2,4,6,8,10,即 10!中至少含有 10/2^1 个因子 2

将整个式子/2
◆1*1*3*2*5*3*7*4*9*5 中,仍然含有因子 2 的数为:2(原4/2),4(原8/2),即 10!中还含有 10/2^2 个因子 2

再将整个式子/2
◆1*1*3*1*5*3*7*2*9*5 中,仍然含有因子 2 的数为:2(原8/4),即 10!中仍然含有 10/2^3 个因子 2

其他数字仍可类比证明

----------------------------------------------------------

代码

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <set>
#include <queue>
#include <stack>
#include <vector>
#include <map>
#define hashsize 1000003
#define inf 0x7f7f7f7f
#define mo 100000007
using namespace std;
bool isp[1000005];
vector<int>p;
int e[1000005];
long long arrangement=1;
int getzys(int n,int pool)
{
	for(int k=0;k<p.size() && p[k]<=n;k++)
	{
		long long pp=p[k];
		if(pool==1)
		while(pp<=n) e[k]=e[k]+n/pp,pp=pp*p[k];
		else
		while(pp<=n) e[k]=e[k]-n/pp,pp=pp*p[k];
	}
}
int main()
{
	int m,n; 
	long long ans=1;
	memset(isp,true,sizeof(isp));
	isp[0]=isp[1]=false;
	for(int i=2;i<=1000005;i++)
		if(isp[i])
		{
			p.push_back(i);
			for(int k=2;k*i<=1000005;k++) isp[k*i]=false;
		}
	scanf("%d%d",&n,&m);
	for(int i=n;i>=n-m+1;i--) arrangement=arrangement*i%mo;
	cout<<arrangement<<endl;
	memset(e,0,sizeof(e));
	getzys(n,1);
	getzys(m,-1);
	getzys(n-m,-1);
	for(int k=0;p[k]<=n;k++)
	{
		long long t=1;
		for(int i=1;i<=e[k];i++) t=(t*p[k])%mo;
		ans=(ans*t)%mo;
	}
	cout<<ans;
	return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值