题目链接

参考代码

...
int n,k,e,ui,vi;
int head[MAXN],dp[MAXN][25];

void preDfs(int u,int f)
{
	for (int i = head[u];i;i = info[i].nxt)
	{
		int v = info[i].to;
		if (v == f) continue;
		preDfs(v,u);
		for (int j = 1;j <= k;++j) dp[u][j] += dp[v][j - 1];
	}
}

void postDfs(int u,int f)
{
	for (int i = head[u];i;i = info[i].nxt)
	{
		int v = info[i].to;
		if (v == f) continue;
		for (int j = k;j >= 1;--j)
		{
			if (j != 1) dp[v][j] += dp[u][j - 1] - dp[v][j - 2];
			else dp[v][j] += dp[u][0];
		}
		postDfs(v,u);
	}
}

void init()
{
	scanf("%d%d",&n,&k);
	for (int i = 1;i <= n - 1;++i)
	{
		qread(ui);
		qread(vi);
		addedge(ui,vi);
		addedge(vi,ui);
	}
	for (int i = 1;i <= n;++i)
	{
		qread(dp[i][0]);
		for (int j = 1;j <= k;++j) dp[i][j] = dp[i][0];
	}
}

void work()
{
	preDfs(1,0);
	postDfs(1,0);
	for (int i = 1;i <= n;++i)
	{
		qwrite(dp[i][k]);
		putchar('\n');
	}
}

分析

  • 树形DP
  • dp[i][k]:从节点i开始,向外走k步所能访问到的奶牛数。

我们可以很容易地得到从一个节点向下走k步可以访问到的奶牛数(preDfs()),但是,根据题意,我们还要知道向上走k步可以访问多少。这该怎么办呢?

解题报告:洛谷 P3047 [USACO12FEB]附近的牛Nearby Cows
从图中可以看到,所谓向上走,就是要求V4V_4节点在绿色大圈的范围内可以访问到多少奶牛。

加以思考可以发现:这个值就是从V4V_4的父亲V2V_2开始,向上下走k1k-1步可以访问到的奶牛数。

此时,从V2V_2向上走的结果,就是相同子问题,对于我们来说,相同子问题就不是问题,由于我们是从上到下更新的,在遍历到V2V_2前,这个值已经被计算出。

V2V_2向下走,有可能回到V4V_4,这部分形成了重复,因此要将其剔除。怎么办呢?可以想到,V4V_4的父亲V2V_2开始向下走k1k-1步可以访问到的奶牛数,就等于从V4V_4直接向下走k2k-2步可以访问到的奶牛数(上去了又下来,需要两步),而这个值是已知的,将其减去即可。于是得到了转移方程:

if (j != 1) dp[v][j] += dp[u][j - 1] - dp[v][j - 2];
else dp[v][j] += dp[u][0];
//虽然判不判j == 1都是可以AC的,但当j == 1时出现负下标值,故特判。

注意

postDfs()中:

for (int j = k;j >= 1;--j)

为什么要从大到小更新呢?

作者在参考题解时,发现有两种状态表示:一种为dp[i][k][0/1],0和1分别指向上向下走;还有就是作者的方法,直接表示向上下走的总和。

如果不从大到小更新,那么当j == 1时,已经将节点v向上下走的答案求出来了,后面在j == 3时,本来dp[v][j - 2]在转移方程中,只代表向下走的答案,现在却变成了向上下走,这就出了问题。

当然,采用前一种方法,就不会有这样的问题了。

若有谬误,敬请斧正。

相关文章: