参考代码
...
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步可以访问多少。这该怎么办呢?
从图中可以看到,所谓向上走,就是要求节点在绿色大圈的范围内可以访问到多少奶牛。
加以思考可以发现:这个值就是从的父亲开始,向上下走步可以访问到的奶牛数。
此时,从向上走的结果,就是相同子问题,对于我们来说,相同子问题就不是问题,由于我们是从上到下更新的,在遍历到前,这个值已经被计算出。
从向下走,有可能回到,这部分形成了重复,因此要将其剔除。怎么办呢?可以想到,从的父亲开始向下走步可以访问到的奶牛数,就等于从直接向下走步可以访问到的奶牛数(上去了又下来,需要两步),而这个值是已知的,将其减去即可。于是得到了转移方程:
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]在转移方程中,只代表向下走的答案,现在却变成了向上下走,这就出了问题。
当然,采用前一种方法,就不会有这样的问题了。