ZOJ-2342 Roads 二分图最小权值覆盖

题意:给定N个点,M条边,M >= N-1。已知M条边都有一个权值,已知前N-1边能构成一颗N个节点生成树,现问通过修改这些边的权值使得最小生成树为前N条边的最小改动总和为多少?

分析:由于计算的最小改动且为最小生成树则显然前N-1条边肯定权值都减少,后面的边权值都增加。由于选择的边为前N-1得到最小生成树,因此首先将N-1条边构图,然后对后面的每一条边,那么这条边所构成的环中,有任意一条边的a与该边b,设原始权重为w[a],w[b],改变量为d[a],w[b],则有w[a] - d[a] <= w[b] + d[b],移项后有w[a] - w[b] <= d[a] + d[b],而我们要求的 d[] 数组的和值的最小值。可以将所有的 d[x] 看作是x节点(边)的权值,则此题相当于给每个节点(边)分配一个权值,使得满足所有环中的不等式,也就是求一个最小权值匹配,其对偶问题就是构图之后的最大权值匹配问题,可行标的值就是需要分配的权值。本题有一个隐含的条件就是d[i] + d[j] >= 0表现在图中就是每个点对之间连了一条权值为0的边,这样才能够保证求出来的值都是正值。

简要分析其原理:首先最大权值匹配算法的初始化就是选择的最大的边设定可行标,且算法在始终不会改变已经匹配边的左右可行标之和的情况下,不停的扩充权值更小的边进入子图,因此其最后得到的最大匹配一定能够保证所有的边左右端点可行标之和大于等于该边权值。其次,如果一个匹配不是最大匹配,那么既然存在最大匹配那么说明肯定有某条本应该在最大匹配中的边没有加入到子图中且该边的左右端点可行标之和小于该边的权值,否则与假设矛盾。

#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;

const int N = 65;
const int M = 405;
const int inf = 0x3f3f3f3f;
int cn[N][N]; // 原图中的边
int w[M][M]; // 二分图中边的关系
int lx[M], ly[M];
char vx[M], vy[M];
int match[M];
int slack[M];
int n, m, nm;
struct Edge {
    int a, b, c;
}e[M];

bool dfs(int p, int u, int id) {
    for (int i = 1; i <= n; ++i) {
        if (i == p) continue;
        if (cn[u][i] == id) return true;
        if (cn[u][i] != 0 && dfs(u, i, id)) {
            w[cn[u][i]][id-(n-1)] = max(e[cn[u][i]].c - e[id].c, 0);
            return true;
        }
    }
    return false;
}

bool path(int u) {
    vx[u] = 1;
    for (int i = 1; i <= nm; ++i) {
        if (vy[i]) continue;
        if (lx[u] + ly[i] == w[u][i]) {
            vy[i] = 1;
            if (match[i] == -1 || path(match[i])) {
                match[i] = u;
                return true;
            }
        } else {
            slack[i] = min(slack[i], lx[u]+ly[i]-w[u][i]);
        }
    }
    return false;
}

void KM() {
    memset(match, 0xff, sizeof (match));
    memset(ly, 0, sizeof (ly));
    memset(lx, 0x80, sizeof (lx));
    for (int i = 1; i <= nm; ++i) {
        for (int j = 1; j <= nm; ++j) {
            lx[i] = max(lx[i], w[i][j]);
        }
    }
    for (int i = 1; i <= nm; ++i) {
        int cnt = 0;
        while (1) {
            memset(slack, 0x3f, sizeof (slack));
            memset(vx, 0, sizeof (vx));
            memset(vy, 0, sizeof (vy));
            if (path(i)) break;
            int d = inf;
            for (int j = 1; j <= nm; ++j) if (!vy[j]) d = min(d, slack[j]);
            for (int j = 1; j <= nm; ++j) if (vx[j]) lx[j] -= d;
            for (int j = 1; j <= nm; ++j) {
                if (vy[j]) ly[j] += d;
                else slack[j] -= d;
            }
        }
    }
}

int main() {
    int T, a, b, c;
    scanf("%d", &T);
    while (T--) {
        scanf("%d %d", &n, &m);
        memset(cn, 0, sizeof (cn));
        memset(w, 0, sizeof (w));
        for (int i = 1; i <= m; ++i) {
            scanf("%d %d %d", &a, &b, &c);
            if (i < n) cn[a][b] = cn[b][a] = i;
            e[i].a = a, e[i].b = b, e[i].c = c;
        }
        for (int i = n; i <= m; ++i) {
            cn[e[i].b][e[i].a] = i;
            dfs(0, e[i].a, i);
            cn[e[i].b][e[i].a] = 0;
        }
        nm = max(n-1, m-(n-1));
        KM();
        for (int i = 1; i < n; ++i) printf("%d\n", e[i].c-lx[i]);
        for (int i = 1; i <= m-(n-1); ++i) printf("%d\n", e[i+n-1].c+ly[i]);
    }
    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值