「JSOI2014」支线剧情2

「JSOI2014」支线剧情2

传送门
不难发现原图是一个以 \(1\) 为根的有根树,所以我们考虑树形 \(\text{DP}\)
\(f_i\) 表示暴力地走完以 \(i\) 为根的子树的最小代价,那么 \(f_i\) 的计算就很显然了:

\[f_i = \sum_{j \in son_i}f_j + s_j \times dis(i, j) \]

\(s_i\) 表示以 \(i\) 为根的子树的叶子数。
我们再设一个 \(dp_i\) 表示在可以存档读档的条件下走完以 \(i\) 为根的子树的最小代价。
那么我们的转移就是枚举 \(i\) 的一个儿子用来存档或者不存档,然后计算 \(dp_i\) 即可。
参考代码:

#include <cstdio>
#define rg register
#define file(x) freopen(x".in", "r", stdin), freopen(x".out", "w", stdout)
template < class T > inline T min(T a, T b) { return a < b ? a : b; }
template < class T > inline void read(T& s) {
    s = 0; int f = 0; char c = getchar();
    while ('0' > c || c > '9') f |= c == '-', c = getchar();
    while ('0' <= c && c <= '9') s = s * 10 + c - 48, c = getchar();
    s = f ? -s : s;
}
 
typedef long long LL;
const int _ = 1e6 + 5;
 
int tot, head[_]; struct Edge { int v, w, nxt; } edge[_ << 1];
inline void Add_edge(int u, int v, int w) { edge[++tot] = (Edge) { v, w, head[u] }, head[u] = tot; }
 
int n, num[_]; LL f[_], dp[_];
 
inline void dfs(int u, LL dis) {
    if (head[u] == 0) { num[u] = 1; return ; }
    LL sum = 0;
    for (rg int i = head[u]; i; i = edge[i].nxt) {
        int v = edge[i].v, w = edge[i].w;
        dfs(v, dis + w), num[u] += num[v], f[u] += f[v] + 1ll * w * num[v];
        sum += min(f[v] + 1ll * w * num[v], dp[v] + dis + w);
    }
    dp[u] = f[u];
    for (rg int i = head[u]; i; i = edge[i].nxt) {
        int v = edge[i].v, w = edge[i].w;
        dp[u] = min(dp[u], sum - min(f[v] + 1ll * w * num[v], dp[v] + dis + w) + dp[v] + w);
    }
}
 
int main() {
#ifndef ONLINE_JUDGE
    file("cpp");
#endif
    read(n);
    for (rg int k, x, y, i = 1; i <= n; ++i) {
        read(k);
        while (k--) read(x), read(y), Add_edge(i, x, y);
    }
    dfs(1, 0);
    printf("%lld\n", dp[1]);
    return 0;
}
posted @ 2020-02-02 21:47  Sangber  阅读(134)  评论(0编辑  收藏  举报