bzoj 3246 [ioi2013]Dreaming 贪心

有n个点,由m条边连接,第i条边的边权是wi。这些点和边构成了一个森林。你必须要新建若干条条权值为W天的边,使得原图恰好变成一棵树,并且让任意两个点间最长距离最短。求该通行时间
思路:
首先找出每棵树的直径和中心及其对应半径。
加边的过程一定是在这些中心之间加边。
考虑这些中心的连接方式。
选择一个中心,让其它中心都直接连上这个点肯定是最优的,中心点为最大半径所在的那个点。
则答案要么是原树中的直径,要么是加边以后的新的直径。
新的直径分两种情况,一种是最大半径和次大半径直径直接用一条新边相连,另一种是次大边和第三大边用两条新边相连。

更简单的做法是:将中心点与其它点的半径点连边,在原图上哟,然后跑一次新树的直径。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
typedef long long LL;

const int N = 500010;
int n, m, cnt;
LL w, r, ans;
int head[N], son[N];
LL f[N], g[N];
bool vis[N];
struct Edge
{
    int to, nex; LL v;
    Edge() {}
    Edge(int x, int y, LL z) { to = x, nex = y, v = z; }
}e[N<<1];

inline void addEdge(int x, int y, LL z)
{ e[++ cnt] = Edge(y, head[x], z); 
head[x] = cnt; 
}
inline void dfs(int u, int lst) //找出以u为根时,每个点的最长链及次长链 
{
    vis[u] = 1; 
    f[u] = g[u] = 0;
    for(int i = head[u]; i; i = e[i].nex) 
    {
        int v = e[i].to; 
        if(v == lst) continue;
        dfs(v, u);
        if(f[v]+e[i].v > f[u]) 
        son[u] = v, f[u] = f[v]+e[i].v;
    }
    for(int i = head[u]; i; i = e[i].nex)
        if(e[i].to != lst && e[i].to != son[u]) 
            if(f[e[i].to]+e[i].v > g[u]) 
                 g[u] = f[e[i].to]+e[i].v;
}
inline void dfs2(int u, int lst, LL d)
//u代表当前点,lst代表其父亲点,d代表距离
//采用换根法求出每个点到其它点的最长链及次长链 
{
    if(d > f[u]) 
         g[u] = f[u], f[u] = d, son[u] = lst;
    else 
        if(d > g[u]) 
          g[u] = d;
    r = min(r, f[u]); //r代表树的半径,其为所有点的最长链的最小值 
    ans = max(ans, f[u]);//必然会找到某个叶子点,其最长链即为这个树的直径 
    for(int i = head[u]; i; i = e[i].nex) 
    if(e[i].to != lst) 
    {
        if(e[i].to != son[u]) 
               dfs2(e[i].to, u, f[u]+e[i].v);
        else 
            dfs2(e[i].to, u, g[u]+e[i].v);
    }
}
int main()
{
    scanf("%d%d%lld", &n, &m, &w);
    for(int i = 1; i <= m; i ++) 
    {
        int x, y; LL z; 
        scanf("%d%d%lld", &x, &y, &z);
        x ++, y ++; 
        addEdge(x, y, z); addEdge(y, x, z);
    }
    LL mx = -1e18,mx2 = -1e18, mx3 = -1e18;//这边一定要赋最小,-1e9也没用。
    for(int i = 1; i <= n; i ++) 
    if(!vis[i]) 
    {
        r = 1e18; 
        dfs(i, 0); 
        dfs2(i, 0, 0);
        //求出每个树的半径后,求所有半径的最长,次长,及第三长 
        if(r > mx) mx3 = mx2, mx2 = mx, mx = r;
        else if(r > mx2) mx3 = mx2, mx2 = r;
        else if(r > mx3) mx3 = r;
    }
    ans = max(ans, mx+w+mx2);
    ans = max(ans, mx2+mx3+w+w);
    printf("%lld\n", ans);
    return 0;
}

 

posted @ 2019-10-17 21:01  我微笑不代表我快乐  阅读(114)  评论(0编辑  收藏  举报