BZOJ.5305.[HAOI2018]蘋果樹(組合 計數)

LOJ
BZOJ
洛谷
php

BZOJ上除了0ms的Rank1啦。明明這題常數很好優化的。優化


首先,\(n=1\)時有\(2\)個位置放葉子,\(n=2\)時有\(3\)個... 可知\(n\)個點的有標號二叉樹有\(n!\)種。(一個二叉樹的中序遍歷是惟一的,有\(n!\)種,也能夠獲得這個結論)spa

\(Sol1\)
考慮對每條邊兩邊的點集計算貢獻。即設一條邊一邊有\(size\)個點,另外一邊有\(n-size\)個點,那麼它的貢獻是\(size(n-size)\)
直接把邊放到點上,枚舉每一個點\(i\)(邊就是\(i\to fa[i]\)),再枚舉\(size_i\)\(size_i\)就是\(i\)子樹的大小。
考慮此時的方案數。\(i\)子樹和\(i\)子樹外是獨立的。
對於\(i\)子樹,有\(size_i!\)種樹的形態,而標號分配有\(C_{n-i}^{size_i-1}\)種方案(\(i\)子樹內點的編號要\(\geq i\))。因此\(i\)子樹有\(size_i!\times C_{n-i}^{size_i-1}\)種。
對於\(i\)子樹外部,首先構造出\(i\)個點的樹有\(i!\)種方案。而後還有\(n-i-size_i+1\)個點須要放在\(i\)子樹外的任意位置,方案數是\((i+1-2)(i+2-2)...(i+n-i-size_i+1-2)\)。兩個乘起來,就是\(i(i-1)(n-size_i-1)!\)
那麼答案就是\[\sum_{i=2}^n\sum_{size_i=1}^{n-i+1}size_i(n-size_i)size_i!\cdot C_{n-i}^{size_i-1}\cdot i(i-1)(n-size_i-1)!\]code

\(Sol2\)
遞推。考慮由枚舉大小爲\(L,R\)的兩棵左右子樹來獲得\(L+R+1\)個點的樹。那麼知道深度就能夠算兩棵子樹間的距離了。
\(f[i]\)表示\(i\)個節點的樹,全部\(i!\)種可能中,全部點深度的和(根節點深度爲\(1\))。
\(g[i]\)表示\(i\)個節點的樹,全部\(i!\)種可能中,全部點兩兩之間的距離的和。
轉移的時候枚舉左右子樹的大小\(L,R=i-L-1\),有\[\begin{aligned}f[i]&=i*i!+\sum_{L=0}^{i-1}C_{i-1}^L(f[L]*R!+f[R]*L!)\\g[i]&=\sum_{L=0}^{i-1}C_{i-1}^L(g[L]*R!+g[R]*L!+f[L]*R!*(R+1)+f[R]*L!*(L+1))\end{aligned}\]get

這樣\(g[n]\)就是答案啦。io


//16540kb   196ms
#include <cstdio>
#define Mod(x) x>=mod&&(x-=mod)
typedef long long LL;
const int N=2005;
const LL LIM=1ll<<61;

int C[N][N],fac[N],g[N];

int main()
{
    int n,mod; scanf("%d%d",&n,&mod);
    C[0][0]=fac[0]=fac[1]=1;
    for(int i=2; i<=n; ++i) fac[i]=1ll*i*fac[i-1]%mod;
    for(int i=1; i<=n; ++i)
    {
        C[i][0]=C[i][i]=1;
        for(int j=1; j<i; ++j) C[i][j]=C[i-1][j-1]+C[i-1][j], Mod(C[i][j]);
    }
    for(int i=1; i<=n; ++i) g[i]=1ll*i*(n-i)*fac[n-i-1]%mod*fac[i]%mod;
    LL ans=0;
    for(int i=2; i<=n; ++i)
        for(int sz=n-i+1,tmp=i*(i-1); sz; --sz)
            if((ans+=1ll*C[n-i][sz-1]*g[sz]%mod*tmp)>=LIM) ans%=mod;
    printf("%lld\n",ans%mod);

    return 0;
}
相關文章
相關標籤/搜索