组合数取模学习笔记
组合数取模的话,之前多少会一些,能应付一般的题目,而这次遇到了模数为合数的题目,于是就又来学习了一发.
这次看到了一个比较不错的blog:https://blog.csdn.net/skywalkert/article/details/52553048
在这个blog里,其1.3里的内容,有许多不理解的地方,并且3.2及以后的内容,并没有去研究.
这次主要是get到了用crt解决模数为合数的问题,并且还有与其配套使用的模数为质数的幂的问题.
复习了一下crt,crt就是去按照一个正确且比较方便的方法去构造一个解,并且利用了数在模里模外意义不同.
下面给出解决此类问题的代码以及代码注释:
(此代码对应于具体题目,请读者抓住重点)
#include <cstdio> #include <cstring> #include <algorithm> typedef long long LL; const int N=1000010; int n,x,y,mod,P,p,phi,fac[N],num; inline int Pow(int x,int y){ int ret=1; while(y){ if(y&1)ret=(LL)ret*x%P; x=(LL)x*x%P,y>>=1; } return ret; } inline int cnt(int n){ return n?n/p+cnt(n/p):0; //递归处理n的阶乘里p的个数 } inline int sum(int n){ return n?((LL)(n/P?Pow(fac[P],n/P):1)*fac[n%P]%P*sum(n/p)%P):1; //递归处理n的阶乘里刨去p之后,在模P的意义下的结果 //为什么要递归呢? //我们发现,我们预处理的时候,如果预处理的是刨去p的,那么他根本不循环. //因为对于一个含有p的数来说,他刨去p,和他加上P之后再刨去p,是不一样的. //所以说,我们要预处理的是不含p的数的阶乘,也就是说,如果一个数含有p,那就不乘他. //那么我们首先处理的是不含p的,然后去递归含有一个p的,然后是两个的…… //这样答案就对了. //这样好骚啊,一开始由于不知道这个操作而错*N…… //不过这玩意log^2的吧…… } //以上两个函数每次都尼玛可以在一开始处理阶乘的时候处理出来……然后就会跑得飞快…… //不过预处理的话,必须要求n较小,但是递归的话,只要n或P中的一个很小就可以了. #define deal(n,a,b) int a=sum(n),b=cnt(n); inline int C(int n,int m){ if(m>n)return 0; deal(n,a0,b0) deal(m,a1,b1) deal(n-m,a2,b2) b0-=b1+b2; if(b0>=num)return 0; return (LL)a0*Pow(a1,phi-1)%P*Pow(a2,phi-1)%P*Pow(p,b0)%P; } inline int calc(){ int ret=0,i,a,b,c,d; fac[0]=1; for(i=1;i<=P&&i<=n;++i) fac[i]=(i%p)?(LL)fac[i-1]*i%P:fac[i-1]; a=0,b=x,c=(n-y-x)>>1,d=(n+y-x)>>1; while(c>=0){ ret=(ret+(LL)C(n,a+b)*C(a+b,a)%P*C(c+d,c)%P)%P; ++a,++b,--c,--d; } return ret; } int main(){ //freopen("rio.in","r",stdin); scanf("%d%d%d%d",&n,&mod,&x,&y); x=std::abs(x),y=std::abs(y); if(n-y-x<0||((x&1)!=((n+y)&1))){ puts("0"); return 0; } int i,s=mod,ans=0; for(i=2;s>1;++i){ if(i*i>s)i=s; if(s%i==0){ p=i,P=1,num=0; while(s%p==0) ++num,P*=p,s/=p; phi=P/p*(p-1); ans=(ans+(LL)calc()*(mod/P)%mod*Pow(mod/P,phi-1))%mod; } } printf("%d\n",ans); return 0; }
苟利国家生死以, 岂因祸福避趋之。