BZOJ 3656: 异或 (组合数取模 CRT)

http://www.lydsy.com/JudgeOnline/problem.php?id=3656
大意:经过一通推导,问题变成求$$\binom N M \mod P$$,其中N,M<=1e9, P<=1e5,P可以是合数。

参考这位神犇的博客:http://blog.csdn.net/braketbn/article/details/50752153

作为一个蒟蒻,稍微写一点自己的理解...

如果P是素数,我们可以用lucas定理直接解决,那么P是合数应该怎么办呢?

首先,考虑把P分解质因数,拆成$${p_0} ^ {k_0} {p_1}^{k_1} {p_2} ^ {k_2}...$$的形式,再对每一个p^k计算,最后用CRT(中国剩余定理)合并。

然后就是怎么算组合数对质数的幂取模的问题了。我们设$$f(n)=\prod_{i=1}^n i[i\mod p\ne0]\mod {pk}$$,就是1~n中非p的倍数的积对pk取模,那么只要先把n!,m!,(n-m)!里的p的倍数的因子p全部提出来,然后求出f(n),f(m),f(n-m)的值就行了。

求f(n)可以用递归,把n按照p^k分段,整段的可以一起算,零散的一段直接暴力计算,然后乘上f(n/p),表示所有的p的倍数除以p后f的值。总的时间复杂度$$O(P log N)$$。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define TR(x) printf(#x"=%d\n", x)
const int MAXN=100005;
int f[MAXN];
int p[15], k[15], pk[15], s[15], phi[15], np;
int mpow(int a,int b,int m){int r=1;for(;b;b>>=1,a=(ll)a*a%m)if(b&1)r=(ll)r*a%m;return r;}
int calc(int n, int x){
	if(!n) return 1;
	int q=pk[x], t=n/q, r=1;
	if(t){
		for(int i=1; i<q; ++i) if(i%p[x]) r=(ll)r*i%q;
		r=mpow(r,t,q);
	}
	for(int i=n%q; i>=1; --i) if(i%p[x]) r=(ll)r*i%q;
	return (ll)r*calc(n/p[x], x)%pk[x];
}
int cntp(int n, int p){
	int r=0;
	while(n) r+=(n/=p);
	return r;
}
int main(){
	int n, m, P, ans=0;
	scanf("%d%d%d", &n, &m, &P);
	for(int i=2, tp=P; i<=tp; ++i) if(tp%i==0){
		p[np]=i; pk[np]=1;
		while(tp%i==0) tp/=i, k[np]++, pk[np]*=i;
		phi[np]=pk[np]/i*(i-1); np++;
	}
	for(int i=0; i<np; ++i){
		int t=cntp(n,p[i])-cntp(m,p[i])-cntp(n-m,p[i]);
		s[i]=mpow(p[i],t,pk[i]);
		s[i]=(ll)s[i]*calc(n,i)%pk[i];
		s[i]=(ll)s[i]*mpow(calc(m,i),phi[i]-1,pk[i])%pk[i];
		s[i]=(ll)s[i]*mpow(calc(n-m,i),phi[i]-1,pk[i])%pk[i];
	}
	for(int i=0; i<np; ++i){
		ans=(ans+(ll)s[i]*(P/pk[i])%P*mpow(P/pk[i],phi[i]-1,pk[i])%P)%P;
	}
	printf("%d\n", ans);
	return 0;
}
posted @ 2017-03-21 15:24  will7101  阅读(651)  评论(0编辑  收藏  举报