$[ZJOI2010]$排列计数

链接君还是一如既往地不正经

这什么毒瘤玩意儿……

我们通过题解区发现这个要求就是小根堆的性质,考虑枚举树的大小,显然最小的为树根,考虑左右两棵子树的答案相乘即可。(\(l\)\(r\)分别表示左右子树的大小)

\[f[i]={i-1 \choose l}*f[l]*f[r] \]

于是我写了一发,\(TLE\)了两个点……

尼玛还卡快速幂求逆元?(出题人拖出去绕树三匝

可以预处理逆元,这里有一个较好的处理阶乘逆元的方法:这儿是证明

Inv[n]=ksm(Fac[n],p-2);
for(int i=n-1;i>=0;i--) Inv[i]=(Inv[i+1]%p*(i+1)%p)%p;

现在是代码时间~:

#include<bits/stdc++.h>
using namespace std;
#define int long long
inline int read()
{
    int f=1,w=0;char x=0;
    while(x<'0'||x>'9') {if(x=='-') f=-1; x=getchar();}
    while(x!=EOF&&x>='0'&&x<='9') {w=(w<<3)+(w<<1)+(x^48);x=getchar();}
    return w*f;
}
const int N=1e6+10;
int n,p,Fac[N],f[N],Log[N],Inv[N];
inline int ksm(int b,int k)
{
	int a=b;if(!k) return 1;k--;
	while(k)
	{
		if(k&1) a=(a%p*b%p+p)%p;
		b=(b%p*b%p+p)%p;k>>=1;
	}
	return a%p;
}
inline int C(int n,int m)
{
	if(m>n) return 0;
	return ((Fac[n]%p*Inv[m]%p*Inv[n-m]%p)%p+p)%p;
}
inline int LC(int n,int m)
{
	if(!m) return 1;
	return ((C(n%p,m%p)%p*LC(n/p,m/p)%p)%p+p)%p;
}
signed main(){
#ifndef ONLINE_JUDGE
    freopen("A.in","r",stdin);
#endif
	n=read(),p=read();Fac[1]=1;Log[0]=-1;
	for(int i=1;i<=n;i++) Log[i]=Log[i>>1]+1;
	for(int i=2;i<=n;i++) Fac[i]=(Fac[i-1]%p*i%p)%p;
	int l=1,r=1;f[1]=f[2]=1,f[3]=2;Inv[n]=ksm(Fac[n],p-2);
	for(int i=n-1;i>=0;i--) Inv[i]=(Inv[i+1]%p*(i+1)%p)%p;
	for(int i=4;i<=n;i++)
	{
		if(i-(1<<Log[i])+1<=1<<(Log[i]-1)) l++; else r++;//相当于看该节点放在哪颗子树中
		f[i]=(LC(i-1,l)%p*(f[l]%p*f[r]%p+p)%p+p)%p;
	}
	printf("%lld",f[n]);
}
posted @ 2019-10-11 14:43  风骨傲天  阅读(113)  评论(0编辑  收藏  举报