ZOJ 3949 Edge to the Root (树形DP)

版权声明:希望能在自己成长的道路上帮到更多的人,欢迎各位评论交流 https://blog.csdn.net/yiqzq/article/details/82119837

原题地址:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=5568

题意:给出一个棵树,每条边权都为1,要求在点1和点x之间连一条边,使得连边之后,点1到所有点的路径长度之和最小。 输出最小长度之和。

思路:我们可以先进行一遍dfs求出每个节点的子节点数,然后,再用于一个dfs进行状态祝转移.
状态转移解释具体看代码

#include <bits/stdc++.h>
#define eps 1e-8
#define INF 0x3f3f3f3f
#define PI acos(-1)
#define lson l,mid,rt<<1
#define rson mid+1,r,(rt<<1)+1
#define CLR(x,y) memset((x),y,sizeof(x))
#define fuck(x) cerr << #x << "=" << x << endl

using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int seed = 131;
const int maxn = 2e5 + 5;
const int mod = 1e9 + 7;
int t, n;
struct node {
    int v, nxt;
} e[maxn * 2];
int head[maxn], tot, num[maxn];
ll MIN, sum;
int dep[maxn];//dp[i]=j,表示在当前链上深度为i的点是j
ll dp[maxn];//dp[i]=j,表示从根节点向i连一条边的最小总花费
void init_head() {
    for (int i = 0; i <= n; i++) {
        head[i] = -1;
        num[i] = 0;
    }
    MIN = 1e18;
    sum = 0;
    tot = 0;
}
void add_edge(int u, int v) {
    e[tot].v = v;
    e[tot].nxt = head[u];
    head[u] = tot++;
}
int dfs(int u, int d, int fa) {
    num[u] = 1;
    sum += d - 1;
    for (int i = head[u]; ~i; i = e[i].nxt) {
        int v = e[i].v;
        if (v == fa) continue;
        num[u] += dfs(v, d + 1, u);
    }
    return num[u];
}
void dfs2(int u, int d, int fa) {
    for (int i = head[u]; ~i; i = e[i].nxt) {
        int v = e[i].v;
        if (v == fa) continue;
        dep[d + 1] = v;
        dp[v] = dp[u] - 2 * num[v] + num[dep[(d + 2) / 2 + 1]];
        /*
        解释一下转移方程
        因为dp的转移是从u节点到v节点,所以画个图比划一下就知道,由于是从u节点到v节点
        那么使得以v为根节点的子节点的权值都会-1
        所以     dp[u]-num[v]
        然后下面减了,上面相对应的就会比多,
        而多的是以链的中点为根节点的子节点减去以v为根节点的子节点数
        这就是为什么减去的是2倍的num[v]
        最后由于求的是v节点,而v节点的深度是d+1,然后这里的(d + 2) / 2 + 1就是中点,
        具体的可以自己画图研究
        */
        dfs2(v, d + 1, u);
    }
}
int main() {
    scanf("%d", &t);
    while (t--) {
        scanf("%d", &n);
        init_head();
        for (int i = 2; i <= n; i++) {
            int u, v;
            scanf("%d%d", &u, &v);
            add_edge(u, v);
            add_edge(v, u);
        }
        dfs(1, 1, -1); //深度从1开始方便计算
        dp[1] = sum;
        dep[1] = 1;
        for (int i = head[1]; ~i; i = e[i].nxt) {
            /*
            因为要想使总和减少,那么选择与根节点直接相连的没用,
            所以从根节点的子节点的子节点开始遍历dp
            这里有一个很巧妙的方法,我们换一种定义dep深度数组,这样子
            我们就可以直接求中点查询中点的子节点数
            */
            int v = e[i].v;
            dp[v] = sum;
            dep[2] = v;
            dfs2(v, 2, 1);
        }
        for (int i = 1; i <= n; i++) {
            MIN = min(MIN, dp[i]);
        }
        printf("%lld\n", MIN);
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/yiqzq/article/details/82119837