bzoj 2785 jzoj 2755 【2012东莞市选】树的计数(计数+dp):

Description:

给出两个整数nd,求出有n个节点并且两个节点间最长距离为d的标号树的个数。

标号树即是树上每个结点都标有一个不同的编号。

\(n<=50\)

题解:

假设直径是偶数,虽然直径可以有很多条,但是直径的中点是确定的。

奇数也差不多,只是中间那条边是定的。

所以设\(g[i][j]\)表示表示\(i\)个点,定根有标号树,深度是\(j\)的方案数。

然而这个东西dp不了:通常的转移是枚举它的一个子树使深度是\(j-1\),但是这样怎么去重都不行。

所以设\(f[i][j]\)表示深度不超过\(j\)的版本的。

这时,一个点的所有子树都是类似的,那么只要保证选的那个子树含有标号最小的点即可。

注意要预先选出根的颜色,但是\(g[n-x][j]\)又给根分配了颜色,所以要除以n-x。

转移:\(g[i][j]=\sum_{x=0}^{i}g[x][j-1]*g[i-x][j]*C_{i-2}^{x-1}*n/(n-x)\)

最后统计答案:

设\(w=d/2\)

直径是偶数:
相当于要有至少两棵=d/2-1的子树,那么用总数-只有一棵的

\(Ans=g[n][w]-\sum C_{n}^x*f[x][w-1]*g[n-x][w-1]\)

直径是奇数的:
一条边的两边都要=d/2,组合数保证最小的点在一边即可。

\(Ans=\sum C_{n-1}^{x-1}*f[x][w]*f[n-x][w]\)

没有取模,需要高精度。

Code:

#include<bits/stdc++.h>
#define fo(i, x, y) for(int i = x, _b = y; i <= _b; i ++)
#define ff(i, x, y) for(int i = x, _b = y; i <  _b; i ++)
#define fd(i, x, y) for(int i = x, _b = y; i >= _b; i --)
#define ll long long
#define pp printf
#define hh pp("\n")
using namespace std;

const int N = 55;

int n, d;

const int m = 10;

const ll W = 1e8;

struct P {
    ll a[m + 2];
    P() {
        fo(i, 0, m) a[i] = 0;
    }
};

P operator + (P a, P b) {
    fo(i, 0, m) a.a[i] += b.a[i];
    fo(i, 0, m) {
        a.a[i + 1] += a.a[i] / W;
        a.a[i] %= W;
    }
    return a;
}

P operator - (P a, P b) {
    fo(i, 0, m) a.a[i] -= b.a[i];
    fo(i, 0, m) if(a.a[i] < 0) {
        a.a[i] += W;
        a.a[i + 1] --;
    }
    return a;
}

P operator * (P a, P b) {
    P c = P();
    fo(i, 0, m) fo(j, 0, m - i)
        c.a[i + j] += a.a[i] * b.a[j];
    fo(i, 0, m) {
        c.a[i + 1] += c.a[i] / W;
        c.a[i] %= W;
    }
    return c;
}

P operator * (P a, ll b) {
    fo(i, 0, m) a.a[i] *= b;
    fo(i, 0, m) {
        a.a[i + 1] += a.a[i] / W;
        a.a[i] %= W;
    }
    return a;
}

P operator / (P a, ll b) {
    ll y = 0;
    fd(i, m, 0) {
        a.a[i] += y * W;
        y = a.a[i] % b;
        a.a[i] /= b;
    }
    return a;
}

void print(P a) {
    int a0 = m;
    while(a0 >= 0 && !a.a[a0]) a0 --;
    if(a0 < 0) {
        pp("0");
    } else {
        pp("%lld", a.a[a0]);
        fd(i, a0 - 1, 0) printf("%08lld", a.a[i]);
    }
    hh;
}

P c[N][N], f[N][N], g[N][N];

P ans[N][N];

int main() {
    n = 50;
    fo(i, 0, n) {
        c[i][0].a[0] = 1;
        fo(j, 1, i) c[i][j] = c[i - 1][j] + c[i - 1][j - 1];
    }
    fo(j, 0, n) f[1][j].a[0] = g[0][j].a[0] = 1;
    fo(i, 2, n) {
        fo(j, 1, n) {
            fo(x, 1, i - 1) {
                f[i][j] = f[i][j] + f[x][j - 1] * f[i - x][j] * c[i - 2][x - 1] * i / (i - x);
            }
        }
    }
    fo(i, 0, n) {
        fo(j, 1, n) g[i][j] = f[i][j] - f[i][j - 1];
        g[i][0] = f[i][0];
    }
    for(n = 1; n <= 50; n ++) {
        if(n == 1) ans[n][0].a[0] = 1;
        for(d = 1; d <= n; d ++) {
            int w = d / 2;
            if(d & 1) {
                fo(x, 1, n) ans[n][d] = ans[n][d] + g[x][w] * g[n - x][w] * c[n - 1][x - 1];
            } else {
                ans[n][d] = g[n][w];
                fo(x, 0, n) ans[n][d] = ans[n][d] - g[x][w - 1] * f[n - x][w - 1] * c[n][x];
            }
        }
    }
    while(scanf("%d %d", &n, &d) != EOF)
        print(ans[n][d]);
}
上一篇:记第一个问题——python文件无法写入数据


下一篇:TLE 9461V33