P3047 [USACO12FEB] Nearby Cows G(树形换根dp)

给你一棵 n 个点的树,点带权,对于每个节点求出距离它不超过 k 的所有节点权值和 mi​。

第一行两个正整数 n,k。
接下来 n−1 行,每行两个正整数 u,v,表示 u,v 之间有一条边。
最后 n 行,每行一个非负整数 ci​,表示点权。

输出格式

* Lines 1..N: Line i should contain the value of M(i).

输出 n 行,第 i 行一个整数表示 mi​。

输入输出样例

输入 #1复制

6 2 
5 1 
3 6 
2 4 
2 1 
3 2 
1 
2 
3 
4 
5 
6 

输出 #1复制

15 
21 
16 
10 
8 
11 

说明/提示

There are 6 fields, with trails connecting (5,1), (3,6), (2,4), (2,1), and (3,2). Field i has C(i) = i cows.

Field 1 has M(1) = 15 cows within a distance of 2 trails, etc.

【数据范围】
对于 100%100% 的数据:1≤n≤105,1≤k≤20,0≤ci​≤1000

解析:

第一次dfs,先从根节点为1时,开始遍历的的子树,记录它的子树。

第二次dfs,进行根的转换,记录其总和。

利用容斥原理,避免多次相加同一个数,这时的   dp2[v][j] += dp2[x][j-1] - dp1[v][j-2]; 的含义就出来了,dp2[v][i]因为根节点处理好了,所以直接处理子节点。利用根节点转换。

dp2[x][j-1] 表示的是因为x 距离子节点v为1距离,所以我们应该得到的是以x为中心,距离为小于等于 j-1的那些点。

dp1[v][j-2]是以v为中心距离,以v为根,距离为j-2那些点,最后删去,得到的就是以v为中心距离为小于等于 j 的哪些点

#include
using namespace std;
const int N = 1e5 + 5;
int n,val[N],k;

vector g[N];

int dp1[N][22],dp2[N][22];

void dfs1(int x,int fa)
{
	for(int i = 0;i <= k;i++)
	{// dp1[i][j] 表示 在以 i为 根 ,距离不超过 j 的 总和 
		dp1[x][i] = val[x];
	}
	for(int i =0;i < g[x].size();i++)
	{
		int v = g[x][i];
		if(v != fa)
		{
			dfs1(v,x);
			for(int j = 1;j <= k;j++)
			{
				dp1[x][j] += dp1[v][j-1];// 只是记录下面节点的 
			}
		}
	}
	return;
}

void dfs2(int x,int fa)
{
	for(int i= 0;i < g[x].size();i++)
	{
		int v = g[x][i];
		if(v!= fa)
		{
			dp2[v][1] += dp1[x][0];
			for(int j = 2;j <= k;j++)
			{
				dp2[v][j] += dp2[x][j-1] - dp1[v][j-2];// dp1 是以 v为根距离为 j-2的那些点 
				// dp2[x][j-1] 减去根节点的差距 
			}
			dfs2(v,x);
		}
	}
}
int main()
{
	scanf("%d%d",&n,&k);
	for(int i = 1;i < n;i++)
	{
		int u,v;
		scanf("%d%d",&u,&v);
		g[u].push_back(v);
		g[v].push_back(u);
	}
	for(int i = 1;i <= n;i++)
	{
		scanf("%d",&val[i]);
	}
	dfs1(1,0);
	for(int i = 1;i <= n;i++)
	{
		for(int j = 0;j <= k;j++)
		{
			dp2[i][j] = dp1[i][j];
		}
	}
	dfs2(1,0);
	for(int i = 1;i <= n;i++)
	{
		cout << dp2[i][k]<<'\n';
	}
	return 0; 
}

你可能感兴趣的:(树,动态规划,搜索,算法,c++)