在数字较大时快速计算排列数A(n, m)与组合数C(n, m)取模质数后的结果

在计算A(n,m) 或 C(n,m) 时,如果n和m特别大的话,一般题目会要求你得到这个结果取模于一个特别大的质数后的结果,暴力求解对于计算C(n,m)是一件困难的事,而且如果题目要求计算大量的排列数和组合数的话,暴力法特别慢。

下面分享一种快速计算排列数和组合数的方法,要求取模的数必须大,而且是质数,该方法在进行预处理过后,得到每一个排列数和组合数的时间复杂度为 O(1) 。废话不多说,直接给出代码,当模板记下。

#include <bits/stdc++.h>
#define int long long
using namespace std;

const int mod=998244353,N=1e7+5;

int n,a[N],b[N];

int A(int n,int m)
{
	return a[n]*b[n-m]%mod;
}

int C(int n,int m)
{
	return a[n]*b[n-m]%mod*b[m]%mod;
}

int qsm(int a,int b)
{
	int res=1;
	while(b)
	{
		if(b&1) res=res*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return res;
}

void QWQ(int n)
{
	a[0]=1;
	for(int i=1;i<=n;i++) a[i]=a[i-1]*i%mod;
	b[n]=qsm(a[n],mod-2);
	for(int i=n-1;i>=0;i--) b[i]=b[i+1]*(i+1)%mod;
}

signed main()
{
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	
	QWQ(1e7);// 预处理 
	
	int x,y;
	cin>>x>>y;
	
	cout<<A(x,y)<<endl<<C(x,y);
	
	return 0;
}

下面给出一个例题,是icpc真题^_^

原题链接:Alice and Bob - QOJ 6736 - Virtual Judge

题解:

题意:如果p1=k,那么可以对区间[1,k]进行任意顺序重排列,谁先用两次相同的p1,谁就输。

对于样例10:

(1)p1=1 ==> 只可以对区间[1,1]进行重排,分析可知先手必输共有A(9,9)种。

(2)p1=2 ==> 可以对区间[1,2]进行操作,假设区间[1,2]为2 1那么Alice的最优操作是将1移动到第一位,那么Bob输,否则无论Alice如何操作,都会将一个大于等于2的数移动到第一位,那么Bob只需再将2移动到第一位,就会导致Alice使用2次2,导致Alice输,即Bob赢,共有C(8,1)*A(8,8)。

(3)同理,如果p1=k,如果区间[1,k]中存在比k小的,那么最优操作都是将比k小的移动到第一位,并且将k移动到第k位上,因为这样就能够保证下一个人无法再将k移动到第一位,导致‘我’使用两次k。通过分析最后还是Bob输。相对的,如果区间[1,k]都是大于等于k的数,那么Bob就会赢。

分析Bob赢的种数:k=3 ==> C(7,1)*C(6,1)*A(7,7)

   k=4 ==>C(6,1)*C(5,1)*C(4,1)*A(6,6)     k=5 ==>C(5,1)*C(4,1)*C(3,1)*C(2,1)*A(5,5)

       k=6,7,8,9,10 ==>0

综上所述,答案为for (int i = n / 2; i <= n - 1; i++) ans += A( n - i - 1,i) * A(i,i);

题解代码:

#include <bits/stdc++.h>
#define int long long
using namespace std;

const int mod=998244353,N=1e7+5;

int n,a[N],b[N];

int A(int n,int m)
{
	return a[n]*b[n-m]%mod;
}

int qsm(int a,int b)
{
	int res=1;
	while(b)
	{
		if(b&1) res=res*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return res;
}

signed main()
{
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>n;
	a[0]=1;
	for(int i=1;i<=n;i++) a[i]=a[i-1]*i%mod;
	b[n]=qsm(a[n],mod-2);
	for(int i=n-1;i>=0;i--) b[i]=b[i+1]*(i+1)%mod;
	
	int ans=0;
	for(int i=n/2;i<=n-1;i++)
		ans=(ans+A(i,n-i-1)*a[i])%mod;
	
	cout<<ans;
	
	return 0;
}

  • 6
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值