組合數取模的話,以前多少會一些,能應付通常的題目,而此次遇到了模數爲合數的題目,因而就又來學習了一發.
此次看到了一個比較不錯的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; }