bzoj2111 perm 排列计数
Perm 排列计数
内存限制:512 MiB 时间限制:1000 ms 标准输入输出
题目描述
输入格式
输出格式
样例
数据范围与提示
事实上题目少说一句i为奇数/2就向下取整(虽然说不说都可以推断,说了比较严谨)
既然这样我们可以把它想象成一个 二叉堆(小根堆) 满足magic 就是满足二叉堆 那么magic的方案数就是满足二叉堆的方案数。
但如果暴力计算方式会TLE,我们需要用一种别的方式计算
于是就有了树形dp 设f[i]为以i为根的方案数显然可以由两个儿子贡献过来
当前点因为根是最小的所以size[i]-1表示剩余的节点数 然后是在size[i]-1的范围内选size[left]数量的数分配给左儿子
于是
$f[i]=f[(i<<1)]*f[(i<<1|1)]*{C_{size[i]-1}^{size[i<<1]}}$
由于n比较大然后要用lucas定理
然后注意当前点不能大于n转移就完了
下面依然是本人丑陋的代码
#include<bits/stdc++.h> #define ll long long #define A 1100000 #define maxn 1000010 #define p 1000000007 using namespace std; ll m,n,k,f[A],jie[A],ermi[A],ans,ni[A]; inline ll read() { ll f=1,x=0;char c=getchar(); while(!isdigit(c)){if(c=='-') f=-1;c=getchar();} while(isdigit(c)){x=(x<<1)+(x<<3)+c-'0';c=getchar();} return f*x; } ll meng(ll x,ll k) { ll ans=1; for(;k;k>>=1,x=x*x%p) if(k&1) ans=x%p*ans%p; return ans; } ll C(ll n,ll m) { if(m==0) return 1; if(m>n) return 0; else return (jie[n]*ni[m]%p*ni[n-m])%p; } void init() { n=read(),k=read(); m=n-k; jie[0]=1;ni[0]=1;ermi[0]=1; for(ll i=1;i<=maxn;i++) ermi[i]=2*ermi[i-1]%(p-1);//2^ermi[i]%p!=2^ermi[i]%p for(ll i=1;i<=n;i++) jie[i]=jie[i-1]*i%p; ni[n]=meng(jie[n],p-2); for(ll i=n-1;i>=1;i--)ni[i]=ni[i+1]*(i+1)%p; for(ll i=0;i<=n;i++) f[i]=C(n,i)%p*(meng(2,ermi[n-i])%p-1)%p; } int main() { // freopen("test.in","r",stdin);freopen("vio.out","w",stdout); ans=0; init(); for(ll i=k;i<=n;i++) ans=(C(i,k)*f[i]%p*((i-k)&1?-1:1)+ans)%p; cout<<(ans%p+p)%p<<endl; }
当然也可以线性转移
for(int i=n;i;--i) { if(sz[i]<3) dp[i]=1; else dp[i]=1ll*dp[lch]*dp[rch]%mod*lucas(sz[i]-1,sz[lch])%mod; //不能给右儿子算组合数,可能越界 }
会快很多
我已没有下降的余地