HDU 4126 and HDU 4756 【求最小生成树的最佳替换边】经典题

HDU - 4126HDU - 4756 基本是一样的题, 前面是后面那道的简化版

这两道题主要涉及的一个问题就是, 每次替换掉图中的一条边, 问此时图的最小生成树是多少..
很明显有个暴力的方法, 但是这样时间爆炸, 我们可以这样想, 先求一次图的最小生成树, 如果替换的边不是树边, 那么此时的图的MST依旧不会变, 如果是树边, 很明显, 我们需要在原图中找一条不是树边, 并且尽量小的边去替换, 这样的答案就可以保证是最优的, 所以我们可以预先处理出所有两点的最佳替换边, 具体过程为我们先求出MST, 然后将每个点作为根进行dfs一遍, 维护dp[u][v],dp[u][v] 代表的是切断u - v这条边后,S集(u所在的集合)到T集(v所在的集合)的最短距离。

具体过程如下:

分别以图中每一个点为根,假如当前根是root, 则在最小生成树的图中进行深搜,假如一条搜索路径为,
root ->v1->v2->v3->u,到u搜索不下去了.
则我们倒着考虑,切断v3-u,这个边,则u单独成一个集合,其他n-1个点是
一个结合,此时考录root到u的距离,dp[v3][u] = min(dp[v3][u],dis(root,u));
这样我们有了root-u的距离。然后考虑切断v2-v3,则v3,u是一个集合了,其他n-2
个点成了一个结合,我们也可以得到root-v3,则两个集合的最短距离是min(root-v3, root-u, dp[v2][v3]).
然后考虑切断v1 - v2, 则v2, v3, u成了一个集合, 其他n-3个点是一个集合,
两个集合的最短距离是min(root-v2, root-v3, root-u, dp[v1][v2])
如果切断root-v1, 则v1, v2, v3, u 是一个集合, root自己是一个集合,其最短距离是
min(root-v2,root-v3,root-u,dp[v1][v2]), 这时因为root - v1是构成最小生成树的边,
我们不能把它考虑进去. 按照这个规则, 我们以每个节点为根都进行这样的深搜,得到
断掉最小生成树中的边后,两个集合的最短距离,然后暴力枚举删除最小生成树的边来求解

HDU - 4126 AC代码
题意: q次替换图中的一条边, 问此时图的最小生成树是多少, 然后q次为等概率, 求期望是多少.

const int maxn = 3e3+5;
int dp[maxn][maxn], s[maxn][maxn];
vector<int>g[maxn];
int n, m;
struct edge {
    int  u, v, w;
    bool operator < (const edge &_ ) const {
        return w < _.w;
    }
}e[maxn*maxn];
int fa[maxn], r[maxn];
bool vis[maxn][maxn];
void init() {
    for (int i = 1 ; i <= n ; i ++) {
        fa[i] = i; r[i] = 1;
        g[i].clear();
        for (int j = 1 ; j <= n ; j ++) s[i][j] = dp[i][j] = inf;
        s[i][i] = dp[i][i] = 0;
    }
    Fill(vis, 0);
}
int Find(int x) {
    return fa[x] == x ? x : fa[x] = Find(fa[x]);
}
bool Un(int x, int y) {
    int fx = Find(x);
    int fy = Find(y);
    if (fx == fy) return false;
    if (r[fx] > r[fy]) swap(fx, fy);
    fa[fx] = fy;
    r[fy] += r[fx];
    return true;
}

ll kru() {
    int cnt = 0; ll tot = 0;
    for (int i = 1 ; i <= m ; i ++) {
        if (Un(e[i].u, e[i].v)) {
            ++ cnt;
            g[e[i].u].pb(e[i].v);
            g[e[i].v].pb(e[i].u);
            vis[e[i].u][e[i].v] = vis[e[i].v][e[i].u] = 1;
            tot += e[i].w;
        }
        if (cnt >= n - 1) break;
    }
    return tot;
}
int dfs(int u, int fa, int r) {
    int mi = inf;
    for (auto it : g[u]) {
        if (it == fa) continue;
        int tmp = dfs(it, u, r);
        mi = min(mi, tmp);
        dp[it][u] = dp[u][it] = min(dp[it][u], tmp);
    }
    if (r != fa) mi = min(mi, s[r][u]);
    return mi;
}
void solve() {
    while(~scanf("%d%d",&n,&m)) {
        if (n + m == 0) break; init();
        for (int i = 1 ; i <= m ; i ++) {
            scanf("%d%d%d",&e[i].u, &e[i].v, &e[i].w);
            ++e[i].u; ++e[i].v;
            s[e[i].u][e[i].v] = s[e[i].v][e[i].u] = e[i].w;
        }
        sort(e+1, e+1+m);
        ll tot = kru(), sum = 0;
        for (int i = 1 ; i <= n ; i ++) dfs(i, -1, i);
        int q; scanf("%d", &q); int qq = q;
        while(q--) {
            int u, v, w;
            scanf("%d%d%d", &u, &v, &w);
            ++u; ++v;
            if (!vis[u][v]) sum += tot;
            else sum += tot - s[u][v] + min(dp[u][v], w);
        }
        printf("%.4f\n", 1.0*sum/qq);
    }
}

HDU 4756 AC Code
题意: 有n个点, 第一个点为发电站, 其他点为宿舍, 此时的最小生成树为联通所有宿舍的最低花费, 现在问在原先的最小生成树中去掉宿舍之间的某一条边, 问最多的花费是变为多少乐?

const int maxn = 1e3+5;
int fa[maxn], r[maxn];
int n, m;
int head[maxn], cnt ;
struct node {
    int to, next; db w;
}e[maxn<<1];
int vis[maxn*maxn], mark[maxn];
void init() {
    for (int i = 1 ; i <= n ; i ++) {
        fa[i] = i;
        r[i] = 1;
    }   Fill(mark, 0);
    Fill(vis, 0); Fill(head, -1); cnt = 0;
}
void add(int u, int v, db w) {
    e[cnt] = node{v, head[u], w};
    head[u] = cnt ++;
}
int Find(int x) {
    return fa[x] == x ? x : fa[x] = Find(fa[x]);
}
bool Un(int x, int y)  {
    int fx = Find(x);
    int fy = Find(y);
    if (fx == fy)  return false;
    if (r[fx] > r[fy]) swap(fx, fy);
    fa[fx] = fy;
    r[fy] += r[fx];
    return true;
}
struct Point {
    db x,  y;
}p[maxn];
db dis(int i, int j) {
    return sqrt((p[i].x-p[j].x) * (p[i].x-p[j].x) + (p[i].y - p[j].y) * (p[i].y - p[j].y));
}
db dp[maxn][maxn], G[maxn][maxn];
struct edge {
    int u, v; db w;
    bool operator < (const edge& _) const {
        return w < _.w;
    }
}s[maxn*maxn];
db dfs(int r, int u, int fa) {
    db mi = INF;
    for (int i = head[u] ; ~i ; i = e[i].next) {
        int to = e[i].to;
        if (to == fa) continue;
        db tmp = dfs(r, to, u);
        mi = min(mi, tmp);
        dp[u][to] = dp[to][u] = min(dp[u][to], tmp);
    }
    if (r != fa) mi = min(mi, G[r][u]);
    return mi;
}
edge tt[maxn];
void solve()
{
    scanf("%d%d", &n, &m); init();
    for (int i = 1 ; i <= n ; i ++) {
        scanf("%lf%lf", &p[i].x, &p[i].y);
    }
    int k = 0;
    for (int i = 1 ; i <= n ; i ++) {
        for (int j = i + 1 ; j <= n ; j ++) {
            s[++k] = edge{i,  j, dis(i, j)};
            G[i][j] = G[j][i] = s[k].w;
            dp[i][j] = dp[j][i] = 1.0*INF;
        }
        dp[i][i] = INF; G[i][i] = 0;
    }
    sort(s+1, s+1+k);
    db tot = 0; int cnt = 0, c = 0;
    for (int i = 1 ; i <= k ; i ++) {
        if (Un(s[i].u, s[i].v)) {
            ++ cnt;
            tot += s[i].w;
            add(s[i].u, s[i].v, s[i].w);
            add(s[i].v, s[i].u, s[i].w);
            if (s[i].u != 1) tt[++c] = edge{s[i].u, s[i].v, s[i].w};
            vis[i] = 1;
        }
        if (cnt >= n - 1) break;
    }
    for (int i = 1 ; i <= n ; i ++) {
        dfs(i, i, -1);
    }
    db ans = tot;
    for (int i = 1 ; i <= c ; i ++) {
        ans = max(ans, tot - tt[i].w + dp[tt[i].u][tt[i].v]);
    }
    if (ans == INF) printf("%.2f\n", tot*m);
    else printf("%.2f\n", ans*m);
}

猜你喜欢

转载自blog.csdn.net/Anxdada/article/details/81086041
hdu