题解 P3047 [USACO12FEB]附近的牛Nearby Cows

题解 P3047 [USACO12FEB]附近的牛Nearby Cows

题目链接

题目大意是给你一颗树,对于每一个节点i,求出范围k之内的点权之和

看数据范围就知道暴力肯定是会t飞的,所以我们要考虑如何dp(代码习惯写dfs)

仔细思考一下我们发现点i走k步能到达的点分为以下两种

1.在i的子树中(由i点往下)

2.经过i的父亲(由i点往上)

这样的问题一般可以用两次dfs解决

我们定义状态f[i][j]表示i点往下j步范围内的点权之和,d[i][j]表示i点往上和往下走j步范围内点权之和。

第一次dfs我们求出所有的f[n][k],这个比较简单,对于节点u和其儿子v,f[u][k] += f[v][j - 1]就行了。(第一次dfs已知叶子节点推父亲节点)

第二次dfs我们通过已经求出的f数组推d数组,对于u和u的儿子v, d[v][k] += (d[u][k - 1] - f[v][k - 2]), 注意数组下表不要越界。d[i][j]的初始值应该赋为f[i][j],因为根节点的d[i][j]就是f[i][j]。(第二次dfs已知父亲节点推儿子节点)

思路说得可能有点罗嗦看代码吧:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#define ll long long
#define N 100005
using namespace std;
inline int read()
{
    int x = 0, f = 1;
    char c = getchar();
    while (c < '0' || c > '9') { if (c == '-') f = -1; c = getchar(); }
    while (c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar(); }
    return x * f;
}

ll f[N][25], d[N][25];
//f数组和d数组前面已经说了
int val[N];
int n, k, tot, head[N];

struct edge
{
    int to, next;
    edge() {}
    edge(int x, int y) { to = x; next = y; }
}a[N * 2];
//邻接表存图
void add(int from, int to)
{
    a[++tot] = edge(to, head[from]);
    head[from] = tot;
}

void dfs(int x, int fa)
{
    for (int i = 0; i <= k; i++) f[x][i] = val[x];
    for (int i = head[x]; i; i = a[i].next)
    {
        int u = a[i].to;
        if (u != fa)
        {
            dfs(u, x);//先dfs到叶子节点,然后推父亲节点
            for (int i = 1; i <= k; i++)
                f[x][i] += f[u][i - 1];
        }
    }
}
//第一次dfs
void dfs2(int x, int fa)
{
    for (int i = head[x]; i; i = a[i].next)
    {
        int u = a[i].to;
        if (u != fa)
        {
            d[u][1] += f[x][0];
            for (int i = 2; i <= k; i++)
                d[u][i] += d[x][i - 1] - f[u][i - 2];
            dfs2(u, x);//先dfs父亲节点,更新完儿子后dfs儿子
        }
    }
}
//第二次dfs
int main()
{
    n = read(), k = read();
    for (int i = 1; i < n; i++)
    {
        int a1 = read(), a2 = read();
        add(a1, a2);
        add(a2, a1);
    }
    for (int i = 1; i <= n; i++)
        val[i] = read();
    dfs(1, 1);
    for (int i = 1; i <= n; i++)
        for (int j = 0; j <= k; j++)
            d[i][j] = f[i][j];//把f赋给d
    dfs2(1, 1);
    for (int i = 1; i <= n; i++)
        cout << d[i][k] << endl;//输出答案
    return 0;
}

总之有一类树上dp的题,每个节点的答案与这个节点的父亲和儿子都有关,这时候我们可以分两次dfs求出答案,一般这两次dfs便利顺序不同。这要求大家对树的遍历有非常深的理解和实现能力,但是思路其实并没有那么难。

19.08.31

猜你喜欢

转载自www.cnblogs.com/YuanqiQHFZ/p/11622361.html