假日住宿题解

1.题面

2.前言

太妙了啊太妙了

3.分析

大体思路:枚举每一条边,每一条边都对应着两个点 ( u , v ) (u, v) (u,v) ,从这条边断开后,将出现以 u u u , v v v 为根节点的两颗树,我们就将这条边最大化利用,就是将在左子树的人尽可能多的移动到右子树去(这样一定会经过 e d g e ( u , v ) edge(u, v) edge(u,v) ),右子树同理,由于每个城市只能由一个人,所以在两颗树上的人交换过去的数量为两棵子树节点数量的最小值。

ATTENTION :为什么这样做不会出现一个人多次走一条边的情况呢?因为我们枚举的是边,假设的是让两棵树上的人交换,所以之后我们不会再遍历这条边了,又因为道路是一棵树,所以不会出现环,所以也不会出现走回原点的情况。

4.参考代码

#include <cstdio>
#include <cstring>
#define LL long long

const int MAXN = 1e5 + 5;

int t, n;
LL ans;
LL dp[MAXN];

int len;
int head[MAXN];
struct edge {
    int to, next, val;
}e[MAXN * 2];//切记乘以2

void add (int, int, int);//链式前向星存储边
void dfs (int, int);//树形dp求出以每个节点为根节点的子树的大小
void init ();//初始化

LL Min (LL x, LL y) { return x < y ? x : y; }

int main () {
    scanf ("%d", &t);
    while (t--) {
        init ();
        scanf ("%d", &n);
        for (int i = 1; i < n; i++) {
            int x, y, val; scanf ("%d %d %d", &x, &y, &val);
            add (x, y, val); add (y, x, val);
        }
        dfs (1, -1);
        printf ("%lld\n", ans);
    }
    return 0;
}

void init () {
    memset (head, 0, sizeof head);
    memset (dp, 0, sizeof dp);
    ans = 0; len = 0;
}

void add (int x, int y, int val) {
    e[++len].to = y;
    e[len].next = head[x];
    e[len].val = val;
    head[x] = len;
}

void dfs (int u, int father) {
    dp[u] = 1;
    for (int i = head[u]; i; i = e[i].next) {
        int v = e[i].to, w = e[i].val;
        if (v == father) continue;
        dfs (v, u);
        ans += 2 * w * Min (dp[v], n - dp[v]);//一颗子树的大小为dp[v],另一颗子树的大小就为n - dp[v]了
        dp[u] += dp[v];
    }
}
posted @ 2020-10-04 14:18  C2022lihan  阅读(30)  评论(0编辑  收藏  举报