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("
")
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]);
}
原文地址:https://www.cnblogs.com/coldchair/p/12252233.html