bzoj 2785 jzoj 2755 【2012东莞市选】树的计数(计数+dp):
Description:
给出两个整数n和d,求出有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]);
}
转载注意标注出处:
转自Cold_Chair的博客+原博客地址