zoukankan      html  css  js  c++  java
  • [洛谷P3761] [TJOI2017]城市

    洛谷题目链接:[TJOI2017]城市

    题目描述

    从加里敦大学城市规划专业毕业的小明来到了一个地区城市规划局工作。这个地区一共有ri座城市,《-1条高速公路,保证了任意两运城市之间都可以通过高速公路相互可达,但是通过一条高速公路需要收取一定的交通费用。小明对这个地区深入研究后,觉得这个地区的交通费用太贵。小明想彻底改造这个地区,但是由于上司给他的资源有限,因而小明现在只能对一条高速公路进行改造,改造的方式就是去掉一条高速公路,并且重新修建一条一样的高速公路(即交通费用一样),使得这个地区的两个城市之间的最大交通费用最小(即使得交通费用最大的两座城市之间的交通费用最小),并且保证修建完之后任意两座城市相互可达。如果你是小明,你怎么解决这个问题?

    输入输出格式

    输入格式:

    输入数据的第一行为一个整数n,代表城市个数。

    接下来的n - 1行分别代表了最初的n-1条公路情况。每一行都有三个整数u,v,d。u,v代表这条公路的两端城市标号,d代表这条公路的交通费用。

    1 <= u,v <= n,1<= d <= 2000

    输出格式:

    输出数据仅有一行,一个整数,表示进行了最优的改造之后,该地区两城市 之间最大交通费用。

    输入输出样例

    输入样例#1:

    5
    1 2 1
    2 3 2
    3 4 3
    4 5 4

    输出样例#1:

    7

    说明

    对于30%的数据,1<=n<500

    对于100%的数据,1<=n<=5000

    一句话题意: 给出一颗树,现在可以断开一条边并重新选择一个位置连接使得这张图仍然是一棵树.

    题解: 因为在一棵树上断掉一条边之后,一定会成为两个连通块.那么再在这两个连通块中连一条边,两个连通块重新组成一棵树,那么这时树中的最长的距离就是新组成的直径.

    那么新组成的直径该怎么计算呢?

    显然我们可以分情况讨论:

    1. 断开一条边后形成的两个连通块中一条直径比较长,另一条直径比较短,将直径短的那颗树接到直径较长的树中后新树的直径仍然小于之前较长的直径.
    2. 两条直径差不多长,无论怎么接都会增加长度.此时我们需要它增加的长度尽量小,那么显然是要将两根直径的一半的位置接起来.此时直径可能不能恰好分成平均的两段,我们需要取折半后的较长段的最小值作为半径(想一下为什么).

    那么这样就将所有的合并情况都讨论出来了,我们只需要枚举每一条边断开然后取合并后的最小值就可以了.

    #include<bits/stdc++.h>
    using namespace std;
    const int N = 5000+5;
    const int inf = 2147483647;
    
    int n, ecnt = 1, last[N], ans = inf, f[N], fa[N], q[N], cnt, dist[N], pre[N], dep[N];
    int len[2], L[2], R[2];
    
    struct edge{
        int from, nex, to, w;
    }e[N*2];
    
    void add(int x, int y, int z){
        e[++ecnt].to = y, e[ecnt].w = z, e[ecnt].from = x, e[ecnt].nex = last[x], last[x] = ecnt;
    }
    
    void dfs(int x, int las, int deep, int k){
        dep[x] = deep, fa[x] = las;
        for(int to, i=last[x];i;i=e[i].nex){
            to = e[i].to; if(to == las) continue;
            dfs(to, x, deep+1, k);
            if(len[k] < f[x]+f[to]+e[i].w) len[k] = f[x]+f[to]+e[i].w, L[k] = pre[x], R[k] = pre[to];
            if(f[x] < f[to]+e[i].w) f[x] = f[to]+e[i].w, pre[x] = pre[to];
        }
    }
    
    void get_dis(int x, int lca){
        if(x == lca){ q[++cnt] = x; return; }
        get_dis(fa[x], lca), q[++cnt] = x;
    }
    
    int get(int x, int y){
        cnt = 0 ; int lca, a = x, b = y, mn = inf;
        if(dep[a] < dep[b]) swap(a, b);
        while(dep[a] > dep[b]) a = fa[a];
        if(a == b) lca = a;
        else { while(a != b) a = fa[a], b = fa[b]; lca = a; }
        while(x != lca) q[++cnt] = x, x = fa[x]; get_dis(y, lca);
        for(int i=2;i<=cnt;i++)
            for(int j=last[q[i]];j;j=e[j].nex)
                if(e[j].to == q[i-1]) dist[q[i]] = dist[q[i-1]]+e[j].w;
        for(int i=1;i<=cnt;i++) mn = min(mn, max(dist[q[i]], dist[q[cnt]]-dist[q[i]]));
        return mn;
    }
    
    void solve(int x, int y, int w){
        memset(dist, 0, sizeof(dist)), L[0] = L[1] = R[0] = R[1] = 0;
        memset(f, 0, sizeof(f)), len[0] = len[1] = 0;
        for(int i=1;i<=n;i++) pre[i] = i;
        dfs(x, y, 1, 0), dfs(y, x, 1, 1);
        ans = min(ans, max(get(L[0], R[0])+get(L[1], R[1])+w, max(len[0], len[1])));
    }
    
    int main(){
        ios::sync_with_stdio(false);
        int x, y, z; cin >> n;
        for(int i=1; i<n; i++) cin >> x >> y >> z, add(x, y, z), add(y, x, z);
        for(int i=2; i<=ecnt; i+=2) solve(e[i].from, e[i].to, e[i].w);
        cout << ans << endl;
        return 0;
    }
    
  • 相关阅读:
    动手动脑及作业
    技能——沟通
    大道至简第三章读后感
    编写一个程序,用户输入两个数,求其加减乘除,并用消息框显示计算结果。
    动手动脑及课后实验
    大道至简第六章
    继承与接口
    产生随机数并窗口显示他们的和
    大道至简——失败也是积累
    动手动脑
  • 原文地址:https://www.cnblogs.com/BCOI/p/9516596.html
Copyright © 2011-2022 走看看