sgu206:Roads(KM建模)

非完整版翻译:
给出一棵树与一些其他边,我们对每条边的花费定义为改变后的权值与原来权值之差的绝对值,我们要求以最小的总花费来保证这棵树是最小生成树。输出改变后每条边的权值。总点数 N<=60 ,总边数 M<=400 ,边权 ci<=10000
题解:
乍一看,不怎么会做;再一看,还是不会做,只能参考题解了…
对于每条不属于树的边,加上它后必定会形成一个环,如果原来的tree边要在最小生成树中,那么必须满足 w[i]x[i]<=w[j]+x[j] ( x[k] 表示第k条边的改变量, w[k] 表示第k条边的原始权值,且 i<n && j>=n )。变形一下,可以发现 x[i]+x[j]>=w[i]w[j]
是不是很像KM中的顶标?
没错,就是它。想到这里恭喜你就要做出这题了O(∩_∩)O~
我们对于 i j连一条权值为 w[i]w[j] 的边,补齐二分图中点数少的那一边(因为KM第一保证有完备匹配),然后KM跑一边, Σx 的值就是最小值(因为KM的顶标值和是不断减小的,减到恰好满足题意为止),输出即可。

#include <cstdio>
#include <cstring>
#define MAX(a, b) ((a)>(b)?(a):(b))
#define MIN(a, b) ((a)<(b)?(a):(b))
using namespace std;
const int MAXN = 69;
const int MAXM = 409;
const int INF = 0x3f3f3f3f;

int n, m;
struct Edge
{
    int v, w, ne;   
}e[MAXN<<1];
int head[MAXN], p;
int tree_w[MAXN], else_w[MAXM];
int g[MAXM][MAXM];

inline void add(int u, int v, int w)
{
    e[p].v = v;e[p].w = w;
    e[p].ne = head[u];
    head[u] = p++;
}

inline bool dfs(int cnt, int end, int fa, int val, int id)
{
    if(cnt == end) return true;
    for(int i = head[cnt]; i != -1; i = e[i].ne)
    {
        int to = e[i].v; 
        if(to != fa && dfs(to, end, cnt, val, id))
        {
            g[i>>1][id] = MAX(g[i>>1][id], e[i].w-val);
            return true;
        }
    }
    return false;
}

inline void init()
{
    scanf("%d%d", &n, &m);
    memset(head, -1, n*sizeof(int));
    for(int i = 0; i < n-1; ++i)
    {
        int u, v, w;
        scanf("%d%d%d", &u, &v, &w);
        u--, v--;
        add(u, v, w);add(v, u, w);
        tree_w[i] = w;
    }
    m -= n-1;
    for(int i = 0; i < m; ++i)
    {
        int u, v, w;
        scanf("%d%d%d", &u, &v, &w);
        u--, v--;
        dfs(u, v, -1, w, i);
        else_w[i] = w;
    }
}

int link[MAXM];
int lx[MAXM];
int ly[MAXM];
int slack[MAXM];
bool visx[MAXM];
bool visy[MAXM];
int N;

inline bool find(int cnt)
{
    visx[cnt] = true;
    for(int i = 0; i < N; ++i)
        if(!visy[i])
        {
            int tmp = lx[cnt]+ly[i]-g[cnt][i];
            if(!tmp)
            {
                visy[i] = true;
                if(link[i] == -1 || find(link[i]))
                {
                    link[i] = cnt;
                    return true;        
                }
            }
            else slack[i] = MIN(slack[i], tmp);
        }
    return false; 
}

inline void work()
{
    N = MAX(n-1, m);
    memset(link, -1, N*sizeof(int));
    for(int i = 0; i < N; ++i)
    {
        lx[i] = -INF;
        for(int j = 0; j < N; ++j)
            lx[i] = MAX(lx[i], g[i][j]);
    }
    for(int i = 0; i < N; ++i)
    {
        memset(slack, INF, N*sizeof(int));
        while(1)
        {
            memset(visx, false, N*sizeof(bool));
            memset(visy, false, N*sizeof(bool));
            if(find(i)) break;
            int d = INF;
            for(int j = 0; j < N; ++j) 
                if(!visy[j])
                    d = MIN(d, slack[j]);
            for(int j = 0; j < N; ++j)
                if(visx[j]) lx[j] -= d;
            for(int j = 0; j < N; ++j)
                if(visy[j]) ly[j] += d;
                else slack[j] -= d;
        }
    }
}

inline void print()
{
    for(int i = 0; i < n-1; ++i) printf("%d\n", tree_w[i]-lx[i]);   
    for(int i = 0; i < m; ++i)  printf("%d\n", else_w[i]+ly[i]);    
}

int main()
{
    init();
    work();
    print();
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值