bzoj 2111: [ZJOI2010]Perm 排列计数 Lucas
题意:称一个1,2,...,N的排列P1,P2...,Pn是Magic的,当且仅当2<=i<=N时,Pi>Pi/2. 计算1,2,...N的排列中有多少是Magic的,答案可能很大,只能输出模 P以后的值
题解:有一步特别神得转化:由于对于序列中的元素 $p[i]$ 只限制 $p[i]>p[i/2]$ ,所以可以对原序列构建一个小根堆
对于序列中的 $i$,其在小根堆的父亲是 $\frac{i}{2}$.
那么,我们就将原问题转换为:给定一颗形态固定的小根堆,在小根堆上填上数字,问有多少种填法.
问题转化到这里就简单了:令 $size[i]$ 表示小根堆中 $i$ 的子树大小,$f[i]$ 表示将 $size[i]$ 种不同元素填入 $i$ 子树的方案数.
则有 $f[i]=f[lson]\times \binom{size[i]-1}{size[lson]}\times f[rson]$
因为这是小根堆,所以堆顶元素固定,而给左儿子分配完元素后右儿子得到的元素也就唯一确定了.
#include <bits/stdc++.h>
#define N 2000004
#define LL long long
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
LL mod;
LL fac[N],inv[N],f[N],size[N];
LL qpow(LL x,LL y)
{
LL tmp=1ll;
for(;y;y>>=1,x=x*x%mod) if(y&1) tmp=tmp*x%mod;
return tmp;
}
LL C(int n, int m)
{
if (n<m) return 0ll;
if (n<mod && m<mod) return fac[n]*inv[m]%mod*inv[n-m]%mod;
return C(n/mod,m/mod)*C(n%mod,m%mod);
}
int main()
{
// setIO("input");
int i,j,n;
scanf("%d%lld",&n,&mod);
fac[0]=inv[0]=1ll;
for(i=1;i<=n;++i) fac[i]=fac[i-1]*1ll*i%mod,inv[i]=qpow(fac[i],mod-2);
for(i=0;i<=2*n;++i) f[i]=1ll;
for(i=n;i>0;i--){
size[i]++;
size[i/2]+=size[i];
}
for(i=n;i>=1;--i)
{
if(size[i]==1) continue;
else
{
f[i]=C(size[i]-1,size[i*2])*f[i*2]%mod*f[i*2+1]%mod;
// printf("%d %d\n",size[i]-1,size[i*2]);
// printf("%lld %lld %lld\n",C(i-1,size[i*2]),f[i*2],f[i*2+1]);
// printf("%lld\n",f[i]);
}
}
printf("%lld\n",f[1]);
return 0;
}

浙公网安备 33010602011771号