【TOJ 3750】Building Roads【树形DP】

题意:给出一棵树,树的边有边权,你可以把一条边接到其他地方,但是要保证最后还是一棵树,使得这棵树的直径最小。

思路:很容易想到,我们操作的边肯定是原图中直径的边,我们先找出原图直径的边,然后依次判断操作这条边能获得的新图的直径,从而更新答案。显然要操作的边把树分成了两棵树,要想最后还是一棵树,这条边肯定是接到这两棵树的点上,我们枚举这个端点,可以利用树形dp求出接完边后的直径的长度,对于这两棵子树我们要处理出两个dp值,假设我们对这两棵子树已经用dfs将其转化成有根树,拿一棵子树来讲,dp1[i]表示i节点与非i的孩子子树的点所能构成的最长链的长度,dp2[i][0]表示i节点与i的孩子子树的节点所能构成的最长链的长度,dp2[i][1]表示i节点与i的孩子子树的节点所能构成的次长链的长度。我们对于这两棵树的点进行枚举,假设枚举的点为u,v,那所形成的最长链的长度为:

max(dp1[u]+dp2[u][0],dp1[v]+dp2[v][0], 两棵子树的直径).然后在所有最长链中取最小值就是答案。

现在讲转移方程:如果v不是u与u的孩子字数的节点所能构成的最长链上的点,dp1[v]  = max(dp1[u], dp2[v][0]),否则dp1[v] = max(dp1[u], dp2[v][1])  (v为u的孩子)

dp2的转移直接dfs就可以转移了。

#include <cstdio>
#include <cstring>
#include <queue>
#include <vector>
#include <algorithm>
using namespace std;
#define N 3000

struct E{
    int v, d, ne;
    E(){}
    E(int _v, int _d, int _ne):v(_v), d(_d), ne(_ne){}
}e[N*2];

bool vis[N];
int size, ca = 1, head[N], dis[N], ans;
int pre[N], dp1[N], dp2[N][2], id[N][2], D[N];
queue<int>Q;
vector<int>V[2];

void init() {
    size = 0;
    memset(head, -1, sizeof(head));
}

void add(int u, int v, int d) {
    e[size] = E(v, d, head[u]);
    head[u] = size++;
}

int spfa(int u) {
    memset(dis, 0x3f, sizeof(dis));
    memset(vis, false, sizeof(vis));
    memset(pre, -1, sizeof(pre));
    while (!Q.empty()) Q.pop();
    dis[u] = 0, vis[u] = true;
    Q.push(u);
    int i, v, re = u, mx = 0;
    while (!Q.empty()) {
        u = Q.front(), Q.pop();
        vis[u] = false;
        if (mx < dis[u]) mx = dis[u], re = u;
        for (i = head[u];~i;i = e[i].ne) {
            v = e[i].v;
            if (dis[v] > dis[u]+e[i].d) {
                dis[v] = dis[u]+e[i].d;
                pre[v] = u;
                if (!vis[v]) {
                    Q.push(v);
                    vis[v] = true;
                }
            }
        }
    }
    ans = mx;
    return re;
}

void dfs2(int u, int f, int c) {
    V[c].push_back(u);
    int i, v, d;
    dp2[u][0] = 0, id[u][0] = u;
    for (i = head[u];~i;i = e[i].ne) {
        v = e[i].v, d = e[i].d;
        if (v == f) continue;
        dfs2(v, u, c);
        int ttm = dp2[v][0], ttp = dp2[v][1];
        if (ttm != -1 && ttm+d > dp2[u][0]) {
            dp2[u][1] = dp2[u][0], id[u][1] = id[u][0];
            dp2[u][0] = ttm+d, id[u][0] = v;
        }else if (ttm != -1 && ttm+d > dp2[u][1]) {
            dp2[u][1] = ttm+d, id[u][1] = v;
        }
    }
}

void dfs1(int u, int f) {
    int v, i, d;
    for (i = head[u];~i;i = e[i].ne) {
        v = e[i].v, d = e[i].d;
        if (v == f) continue;
        dp1[v] = dp1[u]+d;
        if (id[u][0] == v && dp2[u][1] != -1) {
            dp1[v] = max(dp1[v], dp2[u][1]+d);
        }else {
            dp1[v] = max(dp1[v], dp2[u][0]+d);
        }
        D[v] = max(dp2[v][0],dp1[v]);
        dfs1(v, u);
    }
}

void cal(int u, int v) {
    memset(dp1, -1, sizeof(dp1));
    memset(dp2, -1, sizeof(dp2));
    memset(id, -1, sizeof(id));
    V[0].clear(), V[1].clear();
    dp1[u] = dp1[v] = 0;
    dfs2(u, v, 0), dfs2(v, u, 1);
    D[u] = dp2[u][0], D[v] = dp2[v][0];
    dfs1(u, v), dfs1(v, u);
    int i, j, d;
    for (i = head[u];~i;i = e[i].ne) {
        if (e[i].v == v) break;
    }
    d = e[i].d;
    int mx = 0;
    for (i = 0;i < V[0].size();i++) {
        u = V[0][i];
        mx = max(dp1[u]+dp2[u][0], mx);
    }
    for (j = 0;j < V[1].size();j++) {
        u = V[1][j];
        mx = max(dp1[u]+dp2[u][0], mx);
    }
    for (i = 0;i < V[0].size();i++) {
        for (j = 0;j < V[1].size();j++) {
            u = V[0][i], v = V[1][j];
            int tm = max(D[u]+D[v]+d, mx);
            ans = min(tm, ans);
        }
    }
}

int main() {
    int T, i, j, u, v, d, n;
    scanf("%d", &T);
    while (T--) {
        scanf("%d", &n);
        init();
        for (i = 1;i < n;i++) {
            scanf("%d%d%d", &u, &v, &d);
            add(u, v, d), add(v, u, d);
        }
        u = spfa(spfa(0));
        while (pre[u] != -1) {
            v = pre[u];
            cal(u, v);
            u = v;
        }
        printf("Case %d: %d\n", ca++, ans);
    }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值