6494. 【GDOI2020模拟03.08】勘探

题目

求大小为\(n\)的直径为\(L\)(指边数)的无标号无根树数量。


思考历程

一开始乱推了一波,后来发现会算重。
于是弃疗。


正解

如果\(L\)为偶数,所有直径的中点是一样的;如果\(L\)为奇数,所有直径的中边是一样的。
这个挺好证明的。

把无根树定了根之后就好做很多了。先考虑\(L\)为奇数,把树从中边切开,形成两棵树,这两棵树都要满足最大深度为恰好为\(\frac{L-1}{2}\)
\(g_{i,j}\)为大小为\(i\),最大深度恰好为\(j\)的树的个数、
这个东西不好算,就设\(f_{i,j}\)为大小为\(i\),最大深度至多为\(j\)的1个数。
转移的时候枚举新增子树大小\(k\)及其个数\(t\)
需要注意的是转移的时候循环变量的枚举顺序,\(j\)放最外层,\(k\)放次外层。可以这么理解:把\(j\)不同的每一层看成个生成函数,计算大小为\(k\)的子树的贡献的时候给整一层计算贡献,不然容易算重。
搞完之后,枚举左右子树大小即可以求出答案(注意大小相等的情况)。

如果\(L\)为偶数,相当于以中点为根的树中,根有至少两个儿子的子树的深度恰好为\(\frac{L}{2}-1\)(已经减去了根到儿子的边)。同样像上面一样求出\(f\)\(g\)
计算答案考虑总体减不合法,总体是\(g_{n,\frac{L}{2}}\),不合法的就是根只有一个儿子的子树深度恰好为\(\frac{L}{2}-1\),枚举其大小\(i\),贡献即为\(g_{i,\frac{L}{2}-1}f_{n-i,\frac{L}{2}-1}\)


代码

using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 210
#define ll long long
int n,L,mo;
ll inv[N];
ll f[N][N],g[N][N];
void dp(int n,int lim){
    for (int j=0;j<=lim;++j)
        f[1][j]=1;
    for (int j=1;j<=lim;++j)
        for (int k=1;k<n;++k){
            ll tmp=f[k][j-1];
            for (int i=n;i>=2 && i>k;--i){
//              if (i==5)
//                  printf("i=5\n");
                ll s=tmp;
                for (int t=1;k*t<i;++t,s=s*(tmp+t-1+mo)%mo*inv[t]%mo)
                    (f[i][j]+=f[i-k*t][j]*s)%=mo;
            }
        }
    for (int i=1;i<=n;++i){
        g[i][0]=f[i][0];
        for (int j=1;j<=lim && j<i;++j)
            g[i][j]=(f[i][j]-f[i][j-1]+mo)%mo;
    }
//  for (int i=1;i<=n;++i,printf("\n"))
//      for (int j=0;j<=lim && j<i;++j)
//          printf("%d ",g[i][j]);
}
int main(){
    freopen("exploit.in","r",stdin);
    freopen("exploit.out","w",stdout);
    scanf("%d%d%d",&n,&L,&mo);
    inv[1]=1;
    for (int i=2;i<=n;++i)
        inv[i]=(mo-mo/i)*inv[mo%i]%mo;
    int lim=L/2;
    dp(n,lim);
    ll ans=0;
    if (L&1){
        for (int i=lim;n-i>=lim && i<=n-i;++i){
            if (i==n-i)
                ans+=g[i][lim]*(g[i][lim]+1)%mo*inv[2]%mo;
            else
                ans+=g[i][lim]*g[n-i][lim]%mo;
        }
        ans%=mo;
    }
    else{
        ans=g[n][lim];
        if (lim>=2)
            for (int i=lim;i<n;++i)
                ans-=g[i][lim-1]*f[n-i][lim-1]%mo;
        ans=(ans%mo+mo)%mo;
    }
    printf("%lld\n",ans);
    return 0;
}

总结

见到无根树的时候要想办法给它定个根。
什么直径的中点中边,或者重心之类的。

猜你喜欢

转载自www.cnblogs.com/jz-597/p/12459440.html