bzoj2111 Perm 排列计数 组合数学

        一开始以为当i为奇数时不需要Pi>Pi/2,翻了一下题解发现/2是整除。。。然后这实际上就是求一颗节点数为n的大根堆有多少种。定义f[i]为这可树种以i为根的子树的方案数,s[i]表示子树的节点数,那么考虑s[i]个数,一定是将最小的给i,剩余的s[i<<1]个给左儿子,这s[i<<1]个可以随机取,因此共有C(s[i]-1,s[i<<1])种,因此得到:

       f[i]=f[i<<1]*f[i<<1|1]*C(s[i]-1,s[i<<1]),最后f[1]位答案。注意边界。

AC代码如下:

#include<iostream>
#include<cstdio>
#define N 2000005
#define ll long long
using namespace std;

int n,p,f[N],s[N],fac[N],inv[N];
int solve(int x,int y){
	if (x<y) return 0; else
	if (x<p) return (ll)fac[x]*inv[y]%p*inv[x-y]%p;
		else return (ll)solve(x/p,y/p)*solve(x%p,y%p)%p;
}
int main(){
	scanf("%d%d",&n,&p); int i; fac[0]=inv[0]=inv[1]=1;
	for (i=1; i<=n && i<p; i++) fac[i]=(ll)fac[i-1]*i%p;
	for (i=2; i<=n && i<p; i++) inv[i]=(ll)inv[p%i]*(p-p/i)%p;
	for (i=2; i<=n && i<p; i++) inv[i]=(ll)inv[i]*inv[i-1]%p;
	for (i=n; i; i--){
		int x=i<<1,y=x|1; s[i]=s[x]+s[y]+1;
		if (x>n) f[i]=1; else if (y>n) f[i]=f[x]; else
		f[i]=(ll)f[x]*f[y]%p*solve(s[i]-1,s[x])%p;
	}
	printf("%d\n",f[1]);
	return 0;
}


by lych

2016.2.24

你可能感兴趣的:(组合,递推,逆元,Lucas定理)