大多数的编程问题都离不开数学问题,而排列组合作为数学的一重要分支,自然也会被列入编程学科里来。
下面给出一道简单例题:
众所周知,不定方程的解有0个或者若干个。
给出方程:
想知道这个不定方程的正整数解和非负整数解各有几个。
(链接:https://ac.nowcoder.com/acm/contest/553/D来源:牛客网)
很容易的,我们通过数学方法能推出正整数解s1=C(m-1,n-1)
非负整数解s2=C(m-1,n+m+1)。
那么我们应该怎样实现它呢?
自然而然,我们会想到用阶乘
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<string>
#include<queue>
using namespace std;
#define maxn 1000005
#define N 1005
#define INF 0x3f3f3f3f
#define ll long long
#define ld long double
const int mod=1e9+7;
ll m,n;
ld s1=1,s2=1; //这里必须要用double,因为下面的计算过程中每过一次都有除法运算
int main()
{
scanf("%lld %lld",&m,&n);
if(m>n)s1=0;
if(m==1){
printf("1 1");
return 0;
}
if(m>1){
int c=n-m;
for(int i=n-1;i>m-1;i--){
s1=s1*i/c; //很多人会问为什么要一边累乘一边除去c,到最后在一起计算不行吗?
c--; //如果不除去的话20的阶乘longlong就炸了
} //虽说我这样做也没能让计算位数达到10^6...
if(m-1>n){
c=n;
for(int i=m+n-1;i>m-1;i--){
s2=s2*i/c;
c--;
}
}
else{
c=m-1;
for(int i=m+n-1;i>n;i--){
s2=s2*i/c;
c--;
}
}
}
printf("%0.Lf %0.Lf",s1,s2);
return 0;
}
既然直接计算解决不了问题,那我们就把目光转向更高等的数学理论——费马小定理
pow(a,p) ≡ a ( mod p)
这里我们把等式两边同除pow(a,2)
得到的式子就是: pow(a,p-2) ≡ 1/a ( mod p)
∴ 1/a mod p=pow(a,p-2) mod p;
所以上面组合数的分母求余后的值就可以计算了~~~
这里应该会有人有疑问为什么求模要这么麻烦,直接把分母分开来计算,累乘的时候求个模就行了。注意!!!这里是重点!!!除法求模不能类似乘法,对于(A/B)mod C,直接(A mod C)/ (B mod C)是错误的;应该要先将B转换成其逆元b=1/B,之后求出(A*b)modC即可;
话不多说,贴上代码(本蒟蒻也是学习借鉴了一下那些大神的代码):
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<string>
#include<queue>
using namespace std;
#define maxn 300005
#define N 1005
#define inf 0x3f3f3f3f
#define LL long long
const int mod=1e9+7;
LL pri[maxn];
LL ni[maxn],ans1,ans2;
LL pow(LL a,int b) //快速幂求逆元时间复杂度为O(logn)
{
LL ans=1,base=a;
while(b)
{
if(b&1)
ans=(base*ans)%mod;
base=(base*base)%mod;
b/=2;
}
return ans;
}
void s() //打个表
{
pri[0]=1;
ni[0]=1;
for(int i=1;i<maxn;i++)
{
pri[i]=pri[i-1]*i%mod;
ni[i]=pow(pri[i],mod-2);
}
}
int main()
{
s();
int n,m;
scanf("%d %d",&m,&n);
ans1=((pri[n-1]*ni[m-1]%mod)*ni[n-m])%mod;
ans2=((pri[n+m-1]*ni[m-1]%mod)*ni[n])%mod;
printf("%lld %lld",ans1,ans2);
return 0;
}