大组合数模100W以内任意整数

那么要求模数为质数的lucas定理就不能用了

我们只能直接算阶乘了

但是对组合数分母来说与模数不互质的话逆元是不存在的

记当前问题为C(n,k) % m

把m分解质因数后得到的m=p1^r1 * p2^r2 *......

C(n,k) % p1^r1

C(n,k) % p2^r2

......

之后把所有结果用中国剩余定理合并就得到原来的结果了

那么现在的问题就是怎么求 C(n,k) % p^r

首先还是要解决互质的问题我们才能算分母的逆元

我们计算出 n! k! (n-k)!分别含有多少个p因子,就可以算出该组合数总的p因子数,如果大于等于r说明是p^r的倍数,取模得0,如果不是那还要往下算

算一个阶乘含多少个p因子的方法是:n/p+n/p/p+n/p/p/p+......

然后就是要算剩下的与p互质的部分了,也就是要算一个不含p因子的阶乘

记mod=p^r

我们可以先预处理出到mod的阶乘,遇到p的倍数先暂时忽略,乘1就可以了,其他的照常乘进去

然后对于n!来说,每mod个数的乘积对mod取模是相等的,就是说 1*2*...*mod == (mod+1)*(mod+2)*(2mod)

当然我说的都是除去含p因子的数之后的,为了方便描述我就先直接这么说了

那么就可以看n能被mod分成多少份,就得到n!=(mod!)^(n/mod) * (n%mod)! * (含p因子的数除去p因子后剩下的东西)

现在问题又缩小到怎么求含p因子的数除去p因子后剩下的东西了

因为 p 除去p剩下1 ,2p除去p剩下2 ,。。。。。。

因为剩下的东西要乘起来,所以就相当于能被p分成多少份,就是它的阶乘,即(n/p)!

这个东西也是要除去含p的因子的,也就是那些p的高次方的数除去一个p后还会剩下一些p

那么就跟我们上面的过程类似,可以n/=p循环求解,把所有结果乘一起

现在得到了不含p因子的阶乘了,那么分母就可以通过欧拉定理求逆了

把分子和分母的逆和一开始求出来的该组合数总的p因子数全部乘一起就得到一条方程的结果了

好了之后剩下的就是中国剩余定理合并所有方程的结果了。


代码:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<string>
#include<iomanip>
#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<vector>
#include<set>
#include<map>
#include<queue>
#include<list>

using namespace std;
typedef long long LL;
typedef unsigned long long ULL;

#define rep(i,k,n) for(int i=(k);i<=(n);i++)
#define red(i,k,n) for(int i=(k);i>=(n);i--)
#define sqr(x) ((x)*(x))
#define clr(x,y) memset((x),(y),sizeof(x))
#define MAX(a,b) ((a)>(b)?(a):(b))
#define MIN(a,b) ((a)>(b)?(b):(a))

LL n,k,m;
LL p[25],r[25];
int pcnt;

LL a[25];

LL quipow(LL x,LL k,LL mod)
{
    LL ret=1;
    while(k)
    {
        if(k&1)ret=ret*x%mod;
        x=x*x%mod;
        k>>=1;
    }
    return ret;
}

int quipow(int x,int k)
{
    int ret=1;
    while(k)
    {
        if(k&1)ret=ret*x;
        x=x*x;
        k>>=1;
    }
    return ret;
}

void getprime(int m)
{
    clr(p,0);
    clr(r,0);
    pcnt=0;
    for(LL i=2;i*i<=m;i++)
    {
        if(m%i==0)
        {
            p[++pcnt]=i;
            while(m%i==0){m/=i;r[pcnt]++;}
        }
    }
    if(m>1)
    {
        p[++pcnt]=m;
        r[pcnt]=1;
    }
}

LL f[1000010];

void pref(int p,int mod)
{
    clr(f,0);
    f[0]=f[1]=1;
    rep(i,2,mod-1)f[i]=f[i-1]*(i%p?i:1)%mod;
}

LL getp(LL n,LL p)
{
    LL ret=0;
    while(n) ret+=(n/=p);
    return ret;
}

LL getf(LL n,LL mod,LL p)
{
    LL ret=1;
    while(n)
    {
        ret=ret*quipow(f[mod-1],n/mod,mod) %mod *f[n%mod] %mod ;
        n/=p;
    }
    return ret;
}

LL getc(LL n,LL m,LL p,LL r)
{
    LL mod=quipow(p,r);
    pref(p,mod);
    LL nump=getp(n,p)-getp(m,p)-getp(n-m,p);
    if(nump>=r)return 0;
    return quipow(p,nump,mod)*getf(n,mod,p)%mod *quipow(getf(m,mod,p)*getf(n-m,mod,p)%mod,(p-1)*quipow(p,r-1)-1,mod)%mod ;
}

LL exgcd(LL a,LL b,LL &x,LL &y)
{
    if(!b)
    {
        x=1;
        y=0;
        return a;
    }
    else
    {
        LL g=exgcd(b,a%b,y,x);
        y-=x*(a/b);
        return g;
    }
}

int CR(int cnt)
{
    LL ret=0,x,y;
    rep(i,1,cnt)
    {
        exgcd(m/p[i],p[i],x,y);
        ret=(ret+m/p[i]*x*a[i])%m;
    }
    return (ret+m)%m;
}

int main()
{
//#define LOCAL
#ifdef LOCAL
	freopen("e:\\read.txt","r",stdin);
	//freopen("e:\\write.txt","w",stdout);
#endif
    while(cin>>n>>k>>m)
    {
        getprime(m);
        rep(i,1,pcnt)
        {
            a[i]=getc(n,k,p[i],r[i]);
            p[i]=quipow(p[i],r[i]);
        }
        cout<<CR(pcnt)<<endl;
    }


	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值