CodeForces 486D.Valid Sets(树形DP)

题意:给出一个n个节点的连通图,问有多少个子集,使得子集中节点的最大值减最小值的差值不大于d。子集不得为空,子集需要是一个连通图。

分析:第一行d,n。((0 <= d <= 2000, 1 <= n <= 2000))。第二行n个数字ai表示每个节点的权值((1 <= ai <= 2000)。)我们可以看出这个不是图,是一棵树,是树,说明可以用树形DP等方式计数,我们用dfs跑每个点作为最大值时的连通图,如果这个联通图中最大值和最小值<=d,说明可以统计起来,即(res = res + res * dfs(j, u))。可以看如下的图,每次循环就加上右边乘以左边的,我们还需要防止重复的计数,即如果一个节点和最大值相同的话,我们需要从大的节点编号走到小的节点编号,可以防止重复计数。

//这个计数方式非常经典,可以记录一下。(点分治的一种算法就采用这种做法)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#include <queue>
#include <algorithm>

using namespace std;
using LL = long long;
const int N = 2005;
const int mod = 1e9 + 7;
int a[N];
int h[N], e[N * 2], ne[N * 2], idx;
void add(int a, int b)
{
	e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

int root, val;
int d, n;

int dfs(int u, int fa)
{
	int res = 1;
	for (int i = h[u]; i != -1; i = ne[i])
	{
		int j = e[i];
		if (j == fa) continue;
		if ((val > a[j] && val - a[j] <= d) || (val == a[j] && j < root))
		{
			res = (res + ((LL)res * dfs(j, u)) % mod) % mod;
		}
	}
	return res;
}

int main()
{		
	scanf("%d%d", &d, &n);

	for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);

	memset(h, -1, sizeof h);
	int u, v;
	for (int i = 1; i < n; ++i)
	{
		scanf("%d%d", &u, &v);
		add(u, v), add(v, u);
	}

	int res = 0;
	for (int i = 1; i <= n; ++i)
	{
		root = i, val = a[i];
		res = (res + dfs(i, -1)) % mod;
	}

	printf("%d
", res);

	return 0;
}
原文地址:https://www.cnblogs.com/pixel-Teee/p/13321463.html