[数论] 组合数取模 中国剩余定理 Lucas定理

C(n,m)%P

Lucas定理

“卢卡斯定理:C(n,m)=C(n%p,m%p)*C(n/p,m/p) mod p 要求p是质数

其中n%p可能会小于m%p 这种情况下直接返回0即可

证明去问卢卡斯 我不知道”

——PoPoQQQ爷

http://blog.csdn.net/popoqqq/article/details/40507373


中国剩余定理


”题目大意:给定n个物品,分给m个人,每个人拿到wi个礼物,问方案数mod P P不一定为质数

首先我们把剩下的礼物也分给一个人 答案明显不变 w[++m]=n-w1-w2-...-wm

然后就会很方便地得到公式:

ans=C(n,w1)*C(n-w1,w2)*C(n-w1-w2,w3)*...*C(n-w1-w2-...-w_(m-1),wm) mod P

       =n!/w1!/w2!/.../wm! mod P

然后p不是质数 我们把P分解 令P=∏pi^ai

我们分别处理,可以得到一次同余方程组ans%pi^ai=lefti,用中国剩余定理合并一下即可。

然后对于每个pi^ai,我们进行以下处理:

将分子和分母化为x*pi^y的形式

然后分母的x部分与pi互质,可以求逆元,分子分母的y部分直接相减即可

然后我们处理阶乘

以19为例,将19!化为x*pi^y的形式,其中pi=3,ai=2 则有

19!%9=(1*2*3*4*5*6*7*8*9*10*11*12*13*14*15*16*17*18*19) %9

           =(1*2*4*5*7*8*10*11*13*14*16*17*19)*3^6*(1*2*3*4*5*6) %9

式子的左半部分是不为3的倍数的数,存在长度为p^a的循环节 求出一个循环节 快速幂处理 然后处理剩余部分

右半部分是6!%9 递归处理即可

我这题解写的真是冷静。。。这题还真TM让人冷静不下来啊-0-“

——PoPoQQQ

http://blog.csdn.net/popoqqq/article/details/39891263

19!%9 这个例子特别好


BZOJ2142

n!/w1!/w2!/.../wm! mod P

然后中国剩余定理合并

#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<iostream>
using namespace std;
typedef long long ll;
typedef pair<ll,ll> data;

inline char nc() 
{
	static char buf[100000],*p1=buf,*p2=buf;
	if (p1==p2) { p2=(p1=buf)+fread(buf,1,100000,stdin); if (p1==p2) return EOF; }
	return *p1++;
}

inline void read(ll &x) 
{
	char c=nc(),b=1;
	for (; !(c>='0' && c<='9'); c=nc()) if (c=='-') b=-1;
	for (x=0; c>='0' && c<='9'; x=x*10+c-'0',c=nc()); x*=b;
}

inline ll Mul(ll a,ll b,ll p)
{
	ll ret=0;
	for (;b;b>>=1,a=(a<<1)%p)
		if (b&1)
			(ret+=a)%=p;
	return ret;
}

inline ll Pow(ll a,ll b,ll p) 
{
	if (b==0) return 1LL;
	ll ret=Pow(a,b>>1,p);
	if (b&1) return ret*ret%p*a%p;
	return ret*ret%p;
}

inline ll EXGCD(ll a,ll b,ll& x,ll &y) //a>=b
{ 
	if (a<b) return EXGCD(b,a,y,x); 
	if (b==0) { x=1; y=0; return a; }
	ll d=EXGCD(b,a%b,x,y);
	ll tmp=y; y=x-a/b*y; x=tmp;
	return d;
}

inline ll Reverse(ll a,ll p)
{
	ll X,Y; EXGCD(a,p,X,Y); return (X%p+p)%p;
}

namespace China {
	ll A[100005],N[100005],C[100005];
	int tot=0;
	ll P=1;
	inline void add(ll a,ll n) {
		A[++tot]=a; N[tot]=n; P*=n;
	}
	inline ll solve() {
		ll X,Y,ret=0,tmp;
		for (int i=1; i<=tot; i++) {
			C[i]=P/N[i];
			X=Reverse(C[i],N[i]);
			(ret+=Mul(Mul(A[i],C[i],P),X,P))%=P;
		}
		return ret;
	}
}

int vst[100005],prime[100005],num;

inline void Pre() 
{
	const int maxn=100000;
	for (int i=2; i<=maxn; i++)
		if (!vst[i]) 
		{
			prime[++num]=i;
			for (int j=i+i; j<=maxn; j+=i) vst[j]=1;
		}
}

ll M,ans;
ll p[50005],a[50005],p_a[50005],cnt;
ll n,m,w[10];

inline void Machine(ll M) 
{
	for (int i=1; i<=num; i++)
		if (M%prime[i]==0) {
			p[++cnt]=prime[i];
			while (M%prime[i]==0) a[cnt]++,M/=prime[i];
			p_a[cnt]=Pow(p[cnt],a[cnt],1000000);
		}
}

inline data Calc(ll x,ll p,ll a,ll p_a) 
{
	ll A=1,B=1;
	for (ll i=1;i<p_a;i++)
		if (i%p)
			(A*=i)%=p_a;
	A=Pow(A,x/p_a,p_a);
	for (ll i=x-x%p_a+1;i<=x;i++)
		if (i%p)
			(A*=i)%=p_a;
	B=x/p;
	if (B)
	{
		data tmp=Calc(B,p,a,p_a);
		(A*=tmp.first)%=p_a;
		B+=tmp.second;
	}
	return make_pair(A,B);
}

int main() {
	Pre();
	freopen("t.in","r",stdin);
	freopen("t.out","w",stdout);
	read(M); Machine(M);
	read(n); read(m);
	ll sum=0;
	for (int i=1; i<=m; i++) read(w[i]),sum+=w[i];
	if (sum>n) printf("Impossible\n"),exit(0);
	if (n-sum) w[++m]=n-sum;
	for (int i=1;i<=cnt;i++)
	{
		data ret=Calc(n,p[i],a[i],p_a[i]);
		for (int j=1;j<=m;j++)
		{
			data tmp=Calc(w[j],p[i],a[i],p_a[i]); 
			ret.second-=tmp.second;
			(ret.first*=Reverse(tmp.first,p_a[i]))%=p_a[i];
		}
		(ret.first*=Pow(p[i],ret.second,p_a[i]))%=p_a[i];
		China::add(ret.first,p_a[i]);
	}
	ans=China::solve();
	cout<<ans<<endl;
	return 0;
}



BZOJ3738

去0时记得乘上2,5的逆元

#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<iostream>
#include<iomanip>
#define P1 512ll
#define P2 1953125ll
using namespace std;
typedef long long ll;
typedef pair<ll,ll> data;

inline char nc() 
{
	static char buf[100000],*p1=buf,*p2=buf;
	if (p1==p2) { p2=(p1=buf)+fread(buf,1,100000,stdin); if (p1==p2) return EOF; }
	return *p1++;
}

inline void read(ll &x) 
{
	char c=nc(),b=1;
	for (; !(c>='0' && c<='9'); c=nc()) if (c=='-') b=-1;
	for (x=0; c>='0' && c<='9'; x=x*10+c-'0',c=nc()); x*=b;
}

inline ll Pow(ll a,ll b,ll p) 
{
	if (b==0) return 1LL;
	ll ret=Pow(a,b>>1,p);
	if (b&1) return ret*ret%p*a%p;
	return ret*ret%p;
}

inline data Plus(data x,data y,ll p)
{
	return make_pair(x.first*y.first%p,x.second+y.second);
}

inline data Minus(data x,data y,ll p,ll phi_p)
{
	return make_pair(x.first*Pow(y.first,phi_p-1,p)%p,x.second-y.second);
}

inline data Find(ll x,ll p,ll p_a,ll* pow)
{
	if (!x) return make_pair(1,0);
	ll tmp=Pow(pow[p_a],x/p_a,p_a)*pow[x%p_a]%p_a;
	return Plus(make_pair(tmp,x/p),Find(x/p,p,p_a,pow),p_a);
}

ll n,m,K,ans;
ll pow1[P1+1],pow2[P2+1];

int main() {
	data ret1,ret2;
	ll X,Y,tmp;
	freopen("t.in","r",stdin);
	freopen("t.out","w",stdout);
	pow1[0]=1; pow2[0]=1;
	for (int i=1;i<=P1;i++) pow1[i]=pow1[i-1]*(i%2?i:1)%P1;
	for (int i=1;i<=P2;i++) pow2[i]=pow2[i-1]*(i%5?i:1)%P2;
	read(n); read(m); read(K);
	
	ret1=Find(n+m,2,P1,pow1);
	ret1=Minus(ret1,Find(n,2,P1,pow1),P1,P1/2*1);
    ret1=Minus(ret1,Find(m,2,P1,pow1),P1,P1/2*1);
    
    ret2=Find(n+m,5,P2,pow2);
    ret2=Minus(ret2,Find(n,5,P2,pow2),P2,P2/5*4);
    ret2=Minus(ret2,Find(m,5,P2,pow2),P2,P2/5*4);
    tmp=min(ret1.second,ret2.second);
    
    X=ret1.first*Pow(2,ret1.second-tmp,P1)%P1*Pow(205,tmp,P1)%P1;
    Y=ret2.first*Pow(5,ret2.second-tmp,P2)%P2*Pow(976563,tmp,P2)%P2;
    ans=(1953125*109*X+512*1537323*Y)%1000000000;
    
    cout<<setfill('0')<<setw(K)<<ans%Pow(10,K,1000000001)<<endl;
	return 0;
}



BZOJ2982

Lucas定理

逆元预处理 (inv[i]=(P-P/i)*inv[P%i])%=P

#include<cstdio>
#include<cstdlib>
#define M 10007
using namespace std;
typedef long long ll;

inline char nc()
{
	static char buf[100000],*p1=buf,*p2=buf;
	if (p1==p2) { p2=(p1=buf)+fread(buf,1,100000,stdin); if (p1==p2) return EOF; }
	return *p1++;
}

inline void read(ll &x)
{
	char c=nc(),b=1;
	for (;!(c>='0' && c<='9');c=nc()) if (c=='-') b=-1;
	for (x=0;c>='0' && c<='9';x=x*10+c-'0',c=nc()); x*=b;
}

inline void write(ll x,char c)
{
	if (x==0) { putchar('0'); putchar(c); return; }
	if (x<0) putchar('-'),x=-x;
	char s[50]={0},len=0;
	while (x) s[++len]=x%10+'0',x/=10;
	for (int i=len;i;i--) putchar(s[i]);
	putchar(c);
}

ll fac[10010],inv[10010];

inline void Pre()
{
	fac[0]=1;
	for (int i=1;i<M;i++)
		fac[i]=fac[i-1]*i%M;
	inv[1]=1;
	for (int i=2;i<M;i++)
		inv[i]=(M-M/i)*inv[M%i]%M;
	inv[0]=1;
	for (int i=1;i<M;i++)
		(inv[i]*=inv[i-1])%=M;
}

inline ll C(ll n,ll m)
{
	if (m>n) return 0LL;
	if (n<M && m<M)
		return fac[n]*inv[m]%M*inv[n-m]%M;
	return C(n/M,m/M)*C(n%M,m%M)%M;
}

int main()
{
	Pre();
	ll Q,n,m;
	freopen("t.in","r",stdin);
	freopen("t.out","w",stdout);
	read(Q);
	while (Q--)
	{
		read(n); read(m);
		write(C(n,m),'\n');
	}
	return 0;
}



BZOJ1951

还得用上费马小定理 

“注意此题有个细节 就是欧拉定理中a与p必须互质 而当a=0(即G=p)时gcd(a,p)=p

所以有一组数据是专门卡这个地方的 这组数据是999911657 999911659 取模后是0^0 于是得到1 但是答案是0 ” ——PoPoQQQ爷

#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<iostream>
#define M 999911659LL
using namespace std;
typedef long long ll;
typedef pair<ll,ll> data; 

inline char nc()
{
	static char buf[100000],*p1=buf,*p2=buf;
	if (p1==p2) { p2=(p1=buf)+fread(buf,1,100000,stdin); if (p1==p2) return EOF; }
	return *p1++;
}

inline void read(ll &x)
{
	char c=nc(),b=1;
	for (;!(c>='0' && c<='9');c=nc()) if (c=='-') b=-1;
	for (x=0;c>='0' && c<='9';x=x*10+c-'0',c=nc()); x*=b;
}

ll prime[4]={2,3,4679,35617};
ll fac[4][36000],inv[4][36000];

inline void Pre()
{
	for (int i=0;i<4;i++)
	{
		ll P=prime[i];
		fac[i][0]=1;
		for (int j=1;j<=P;j++)
			(fac[i][j]=j*fac[i][j-1])%=P;
		inv[i][1]=1;
		for (int j=2;j<=P;j++)
			(inv[i][j]=(P-P/j)*inv[i][P%j])%=P;
		inv[i][0]=1;
		for (int j=1;j<=P;j++)
			(inv[i][j]*=inv[i][j-1])%=P;
	}
}

inline ll C(ll n,ll m,ll P,int t)
{
	if (n<m) return 0LL;
	if (n<P && m<P) 
		return fac[t][n]*inv[t][m]%P*inv[t][n-m]%P;
	return C(n/P,m/P,P,t)*C(n%P,m%P,P,t)%P;
}

inline data EXGCD(ll a,ll b)
{
	if (a<b) { data tmp=EXGCD(b,a); return data(tmp.second,tmp.first); }
	if (b==0) return data(1,0); 
	data tmp=EXGCD(b,a%b);
	return data(tmp.second,tmp.first-a/b*tmp.second); 
}

inline ll Chinese(ll *A,ll *N,ll P,int cnt)
{
	ll ret=0,Mi,Ci;
	for (int i=0;i<cnt;i++)
	{
		Mi=P/N[i];
		Ci=(EXGCD(Mi,N[i]).first%P*Mi%P+P)%P;
		(ret+=Ci*A[i]%P)%=P;
	}
	return ret;
}

inline ll Pow(ll a,ll b,ll p)
{
	if (b==0) return 1LL;
	ll ret=Pow(a,b>>1,p);
	if (b&1) return ret*ret%p*a%p;
	return ret*ret%p;
}

ll N,G,Ans;
ll ans[4];

inline void Calc(ll x)
{
	for (int i=0;i<4;i++)
		(ans[i]+=C(N,x,prime[i],i))%=prime[i];
}

int main()
{
	Pre();
	freopen("t.in","r",stdin);
	freopen("t.out","w",stdout);
	read(N); read(G);
	for (int i=1;i*i<=N;i++)
		if (i*i==N)
			Calc(i);
		else if (N%i==0)
			Calc(i),Calc(N/i);
	Ans=Chinese(ans,prime,M-1,4);
	Ans=Pow(G%M,Ans+M-1,M);
	cout<<Ans<<endl;
}



BZOJ3782

组合数dp

#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<iostream>
#define X first
#define Y second 
using namespace std;
typedef long long ll;
typedef pair<ll,ll> data;
typedef pair<ll,ll> Poi;

inline char nc()
{
	static char buf[100000],*p1=buf,*p2=buf;
	if (p1==p2) { p2=(p1=buf)+fread(buf,1,100000,stdin); if (p1==p2) return EOF; }
	return *p1++;
}

inline void read(ll &x)
{
	char c=nc(),b=1;
	for (;!(c>='0' && c<='9');c=nc()) if (c=='-') b=-1;
	for (x=0;c>='0' && c<='9';x=x*10+c-'0',c=nc()); x*=b;
}

namespace Deal1{
	#define M 1019663265LL
	ll prime[4]={3,5,6793,10007};
	ll fac[4][36000],inv[4][36000];

	inline void Pre()
	{
		for (int i=0;i<4;i++)
		{
			ll P=prime[i];
			fac[i][0]=1;
			for (int j=1;j<=P;j++)
				(fac[i][j]=j*fac[i][j-1])%=P;
			inv[i][1]=1;
			for (int j=2;j<=P;j++)
				(inv[i][j]=(P-P/j)*inv[i][P%j])%=P;
			inv[i][0]=1;
			for (int j=1;j<=P;j++)
				(inv[i][j]*=inv[i][j-1])%=P;
		}
	}
	
	inline ll C(ll n,ll m,ll P,int t)
	{
		if (n<m) return 0LL;
		if (n<P && m<P) 
			return fac[t][n]*inv[t][m]%P*inv[t][n-m]%P;
		return C(n/P,m/P,P,t)*C(n%P,m%P,P,t)%P;
	}
	
	inline data EXGCD(ll a,ll b)
	{
		if (b==0) return data(1,0); 
		data tmp=EXGCD(b,a%b);
		return data(tmp.second,tmp.first-a/b*tmp.second); 
	}
	
	inline ll Chinese(ll *A,ll *N,ll P,int cnt)
	{
		ll ret=0,Mi,Ci;
		for (int i=0;i<cnt;i++)
		{
			Mi=P/N[i];
			Ci=(EXGCD(Mi%N[i],N[i]).first%P*Mi%P+P)%P;
			(ret+=Ci*A[i]%P)%=P;
		}
		return ret;
	}
	
	ll Ans,ans[4];
	
	inline void Calc(ll n,ll m)
	{
		for (int i=0;i<4;i++)
			(ans[i]=C(n,m,prime[i],i))%=prime[i];
	}
	
	inline ll Solve(ll n,ll m)
	{
		Calc(n,m);
		Ans=Chinese(ans,prime,M,4);
		return Ans;
	}
	#undef M
}

namespace Deal2{
	ll P=1000003;
	ll fac[1000005],inv[1000005];

	inline void Pre()
	{
		fac[0]=1;
		for (int j=1;j<=P;j++)
			(fac[j]=j*fac[j-1])%=P;
		inv[1]=1;
		for (int j=2;j<=P;j++)
			(inv[j]=(P-P/j)*inv[P%j])%=P;
		inv[0]=1;
		for (int j=1;j<=P;j++)
			(inv[j]*=inv[j-1])%=P;
	}
	
	inline ll C(ll n,ll m)
	{
		if (n<m) return 0LL;
		if (n<P && m<P) 
			return fac[n]*inv[m]%P*inv[n-m]%P;
		return C(n/P,m/P)*C(n%P,m%P)%P;
	}
	
	inline ll Solve(ll n,ll m)
	{
		return C(n,m);
	}
}

ll N,M,T,P;
Poi point[205];
ll f[205];

inline ll CC(ll n,ll m)
{
	ll ret;
	if (P==1000003)
		ret=Deal2::Solve(n,m);
	else
		ret=Deal1::Solve(n,m);
	return ret;
}

int main()
{
	freopen("t.in","r",stdin);
	freopen("t.out","w",stdout);
	read(N); read(M); read(T); read(P);
	if (P==1000003) Deal2::Pre(); else Deal1::Pre();
	for (int i=1;i<=T;i++)
		read(point[i].X),read(point[i].Y);
	point[++T]=Poi(N,M);
	sort(point+1,point+T+1);
	for (int i=1;i<=T;i++)
	{
		f[i]=CC(point[i].X+point[i].Y,point[i].X);
		for (int j=1;j<i;j++)
			if (point[i].Y>=point[j].Y)
				((f[i]-=f[j]*CC((point[i].X-point[j].X)+(point[i].Y-point[j].Y),(point[i].X-point[j].X))%P)+=P)%=P;
	}
	cout<<f[T]<<endl;
	return 0;
}



BZOJ2111

一开始没看出是个小根堆。。组合数dp

#include<cstdio>
#include<cstdlib>
#include<iostream>
using namespace std;
typedef long long ll;

inline char nc()
{
	static char buf[100000],*p1=buf,*p2=buf;
	if (p1==p2) { p2=(p1=buf)+fread(buf,1,100000,stdin); if (p1==p2) return EOF; }
	return *p1++;
}

inline void read(ll &x)
{
	char c=nc(),b=1;
	for (;!(c>='0' && c<='9');c=nc()) if (c=='-') b=-1;
	for (x=0;c>='0' && c<='9';x=x*10+c-'0',c=nc()); x*=b;
}

ll n,P;
ll fac[1000005],inv[1000005];
ll f[2000005],size[2000005];

inline void Pre()
{
	fac[0]=1;
	for (int i=1;i<=n && i<P;i++)
		(fac[i]=fac[i-1]*i)%=P;
	inv[1]=1;
	for (int i=2;i<=n && i<P;i++)
		(inv[i]=(P-P/i)*inv[P%i])%=P;
	inv[0]=1;
	for (int i=1;i<=n && i<P;i++)
		(inv[i]*=inv[i-1])%=P;
}

inline ll C(ll n,ll m)
{
	if (n<m) return 0LL;
	if (n<P && m<P)
		return fac[n]*inv[m]%P*inv[n-m]%P;
	return C(n/P,m/P)*C(n%P,m%P)%P;
}

int main()
{
	freopen("t.in","r",stdin);
	freopen("t.out","w",stdout);
	read(n); read(P);
	Pre();	
	for (int i=n+1;i<=(n<<1|1);i++)
		f[i]=1;
	for (int i=n;i;i--)
	{
		size[i]=size[i<<1]+size[i<<1|1]+1;
		f[i]=C(size[i]-1,size[i<<1])*f[i<<1]%P*f[i<<1|1]%P;
	}
	cout<<f[1]<<endl;
}






你可能感兴趣的:([数论] 组合数取模 中国剩余定理 Lucas定理)