suxxsfe

一言(ヒトコト)

P3807【模板】卢卡斯定理

题解大部分都是递归实现的,给出一种非递归的形式
话说上课老师讲的时候没给代码,然后自己些就写成了这样

对于质数\(p\)给出卢卡斯定理:

\[\tbinom{n}{m}=\tbinom{n \bmod p}{m \bmod p}\tbinom{\lfloor \frac{n}{p}\rfloor}{\lfloor \frac{m}{p} \rfloor}\pmod p \]

其实它还有另一种形式,虽然本质上没啥区别:

\[\tbinom{n}{m}=\prod_{i=1}^k \tbinom{a_i}{b_i} \pmod p \]

其中,\(a,b\)分别为\(n,m\)\(p\)进制下的每一位,\(k\)是它们位数的较大值,当然如果有数不足\(k\)位,要补前导0


来证明一下
其实证明方法也是看了别人blog才知道的
\(s=\lfloor \dfrac{n}{p}\rfloor,t=\lfloor \dfrac{m}{p}\rfloor\)
则有\(q,w\)使得\(n=sp+q,m=tp+w\)
再考虑一个二项式:

\[(1+x)^n=((1+x)^p)^s(1+x)^q \]

 
先由费马小定理推个结论:

\[x^p\equiv x\pmod p \Rightarrow (x^p+1)\equiv (x+1)\pmod p \]

\[(x+1)^p\equiv (x+1)\pmod p \]

所以:

\[(x+1)^p\equiv (x^p+1)\pmod p \]

 
把这个结论带进去:

\[(1+x)^n\equiv (1+x^p)^s(1+x)^q \pmod p \]

再由二项式定理把右边展开

\[(1+x)^n\equiv \sum_{i=1}^s \tbinom{s}{i}x^{pi}\cdot \sum_{j=1}^q \tbinom{q}{j}x^j \]

同样我们可以把左边展开:

\[(1+x)^n=\sum_{i=1}^{sp+q}\tbinom{sp+q}{i}x^i \]

然后我们可以发现,左右两遍都有\(x^{tp+w}\)次项(当然,这是在\(m\leq n\)的情形下,如果\(m>n\)结果就是0,不用考虑了)
比较一下它们的系数
左边:\(\tbinom{sp+q}{tp+w}x^{tp+w}\)
右边:\(\tbinom{s}{t}x^{tp}\cdot \tbinom{q}{w}x^w\)
这边要说明一下,不会出现别的次数组合,比如\((t-1)p\)\((w+p)\),因为\(w,q<p\)
所以:\(\tbinom{sp+q}{tp+w}\equiv \tbinom{s}{t}\tbinom{q}{w}\pmod p\)
即:

\[\tbinom{n}{m}\equiv\tbinom{n \bmod p}{m \bmod p}\tbinom{\lfloor \frac{n}{p}\rfloor}{\lfloor \frac{m}{p} \rfloor}\pmod p \]

然后把\(\tbinom{\lfloor \frac{n}{p}\rfloor}{\lfloor \frac{m}{p} \rfloor}\)这一项不断展开,其实就变为了那种非递归形式


好了,我们终于得到了这个定理
那写代码就简单了,将\(n,m\)转化为\(p\)进制
预处理出阶乘数组,和阶乘的逆元数组
然后对于这\(p\)进制的每一位直接套组合数公式就行了
然而代码似乎没有递归的好写
另外一共5个点,我错了三遍下载了三个数据来调
那么说一下踩得坑,首先主函数for循环里的特判一定要有,避免出现数组下标变成负数,或者使用0的逆元的情况
还有多测时前导0的位置一定要清零

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<iomanip>
#include<cstring>
#include<stack>
#define reg register
#define EN std::puts("")
#define LL long long
inline int read(){
	int x=0,y=1;
	char c=std::getchar();
	while(c<'0'||c>'9'){if(c=='-') y=0;c=std::getchar();}
	while(c>='0'&&c<='9'){x=x*10+(c^48);c=std::getchar();}
	return y?x:-x;
}
int n,m,p;
LL fac[200006],g[200006];
int a[100006],b[100006];
inline LL power(LL x,LL y){
	reg LL ans=1;
	while(y){
		if(y&1) ans=(1ll*ans*x)%p;
		y>>=1;x=(x*x*1ll)%p;
	}
	return ans;
}
std::stack<int>s;
inline void pre(){//预处理函数
	a[0]=b[0]=0;
	while(n){
		s.push(n%p);n/=p;
	}
	while(!s.empty()) a[++a[0]]=s.top(),s.pop();
	int tmp=0;
	while(m){
		tmp++;
		s.push(m%p);m/=p;
	}
	while(b[0]+tmp<a[0]) b[++b[0]]=0;//前导零的位置一定要清零 
	while(!s.empty()) b[++b[0]]=s.top(),s.pop();
	fac[0]=1;
	for(reg int i=1;i<p;i++) fac[i]=(1ll*fac[i-1]*i)%p;
	g[p-1]=power(fac[p-1],p-2);
	for(reg int i=p-2;i;i--) g[i]=(1ll*g[i+1]*(i+1))%p;
}
int main(){int t=read();while(t--){
	n=read();m=read();p=read();
	n+=m;m=n-m;
//	std::memset(a,0,sizeof a);std::memset(b,0,sizeof b);
	pre();
//		for(reg int i=1;i<=a[0];i++) std::printf("%d ",a[i]);EN;
//		for(reg int i=1;i<=b[0];i++) std::printf("%d ",b[i]);EN;
//		for(reg int i=0;i<p;i++) std::printf("%d ",fac[i]);EN;
//		for(reg int i=0;i<p;i++) std::printf("%d ",g[i]);EN;
	LL ans=1;
//		std::printf("%d %d\n",a[0],b[0]);
	for(reg int i=1;i<=a[0];i++){
		if(!b[i]) continue;
		if(a[i]<b[i]){ans=0;break;}
		if(a[i]==b[i]) continue;
		ans=(1ll*ans*fac[a[i]])%p;
		ans=(1ll*ans*g[b[i]])%p;
		ans=(1ll*ans*g[a[i]-b[i]])%p;
	}
	std::printf("%lld\n",ans);
}
	return 0;
}
posted @ 2020-03-19 21:16  suxxsfe  阅读(175)  评论(0编辑  收藏  举报