1. 程式人生 > >[BZOJ 2111] 排列計數

[BZOJ 2111] 排列計數

-m const 排列 for -- 都是 names printf get

Link:

BZOJ 2111 傳送門

Solution:

小根堆的模型還是很容易能看出來的

利用樹形$dp$統計方案數:$dp[i]=dp[lc]*dp[rc]*C[sz[i]-1][sz[lc]]$

(小根堆的個數只受序列中數的大小關系影響,與其差值無關,因此每一個組合產生相同的個數)

其中組合數的計算全是坑點啊……

需要註意此題中$MOD$的值可能小於$n$

因此直接求組合數以及用費馬小定理算逆元的方法都是錯誤的($fac[i](i>MOD)$均為0!)

於是需要使用$Lucas$定理來計算組合數,來保證$a,b$均小於$MOD$時才調用$fac$和$inv$

此時$fac$和$inv$的上界都應設為$MOD-1$來保證正確性(或者使用另一種線性求1至$n$逆元的方式)

Code:

#include <bits/stdc++.h>

using namespace std;
#define lc (i<<1)
#define rc (i<<1|1)
typedef long long ll;
const int MAXN=1e6+10;
int sz[2*MAXN];
ll n,MOD,fac[MAXN],inv[MAXN],dp[MAXN];

ll quick_pow(ll a,ll b)
{
    ll ret=1;
    for(;b;b>>=1,a=a*a%MOD)
        if(b&1
) ret=ret*a%MOD; return ret; } ll C(ll a,ll b) //n大於MOD時一定要用Lucas { if(a<b) return 0; if(a<MOD&&b<MOD) return fac[a]*inv[b]%MOD*inv[a-b]%MOD; return C(a/MOD,b/MOD)*C(a%MOD,b%MOD)%MOD; } int main() { scanf("%lld%lld",&n,&MOD); fac[0]=1; /* int mn=min(n,MOD-1); 如果n>MOD則要重新設置上限 for(int i=1;i<=mn;i++) fac[i]=fac[i-1]*i%MOD; inv[mn]=quick_pow(fac[mn],MOD-2); for(int i=mn-1;i>=0;i--) inv[i]=inv[i+1]*(i+1)%MOD;
*/ for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%MOD; inv[0]=inv[1]=1;//求1-n逆元的線性做法 for(int i=2;i<=n;i++) inv[i]=(MOD/i+1)*inv[i-MOD%i]%MOD; for(int i=2;i<=n;i++) inv[i]=inv[i]*inv[i-1]%MOD; for(int i=n;i>=1;i--) { sz[i]=sz[lc]+sz[rc]+1; dp[i]=(lc>n?1:dp[lc])*(rc>n?1:dp[rc])%MOD*C(sz[i]-1,sz[lc])%MOD; } printf("%lld",dp[1]); return 0; }

Review:

(1)遇到模數要輸入的題目要敏感,考慮模數是否可能小於$n$

此時必須要利用$Lucas$定理求解組合數,求逆元時上限也要從$n$改為$MOD-1$

(2)小根堆的個數只受序列中數的大小關系影響,與其差值無關

每一個數值兩兩不等的序列能產生的小根堆數相同

[BZOJ 2111] 排列計數