bzoj 1937: [Shoi2004]Mst 最小生成树 (KM算法)

题目描述

传送门

题解

KM算法
可行顶标 对于一个赋值二分图G(i,j,e,w) (i,j代表二分图的两边顶点标号,e代表边,w代表边的权值 ) 我们对两边的每一个顶点都赋予一个额外的值成cx,cy,使得对于二分图G内的所有边均有 cx[i]+cy[j]>=val[i][j]
对于任意一个匹配来说,他所有权值之和就是可行顶标之和,而可行顶标之和必然是权值之和最大的
回到这个题,对于在T中的边只会减少,对于不在T中的边只会增加。
e=(x,y) ,那么要求x,y路径上的说有边满足 widi<=wj+dj ,其中w表示边权,d表示变化量。
将式子移项,得到 di+dj>=wjwi ,可以发现如果把wj-wi看成是权值,那么这个式子很符合KM算法中顶标的相关定义,所以我们可以通过KM算法来求解。
因为是最大权完美匹配,所以两边的点至少有一排应该是全部匹配上的,所以我们将树边和非树边分成两列,从非树边向树边连边权值为边权差,然后对于树边的那一列建立虚点,每个非树边对应一个,权值为0.

不过这道题其实也可以写单纯性。

代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define N 1003
#define inf 1000000000
using namespace std;
int tot,nxt[N],point[N],v[N],len[N],num[N],fa[N],x[N],y[N],c[N],mp[N][N],mark[N];
int n,m,pdx[N],pdy[N],cur[N],cx[N],cy[N],deep[N],val[N][N],cnt,belong[N],slack[N];
void add(int x,int y,int z,int k)
{
    tot++; nxt[tot]=point[x]; point[x]=tot; v[tot]=y; len[tot]=z; num[tot]=k;
    tot++; nxt[tot]=point[y]; point[y]=tot; v[tot]=x; len[tot]=z; num[tot]=k;
}
void dfs(int x,int f)
{
    deep[x]=deep[f]+1;
    for (int i=point[x];i;i=nxt[i]) {
        if (v[i]==f) continue;
        fa[v[i]]=x; cur[v[i]]=i;
        dfs(v[i],x);
    }
}
void check(int i)
{
    int x1=x[i]; int y1=y[i];
    while (x1!=y1) {
        if (deep[x1]>deep[y1]) {
            int t=cur[x1];
            val[cnt][num[t]]=len[t]-c[i];
            x1=fa[x1];
        }
        else {
            int t=cur[y1];
            val[cnt][num[t]]=len[t]-c[i];
            y1=fa[y1];
        }
    }
    val[cnt][n+cnt]=0;
}
bool dfs(int x)
{
    pdx[x]=1;
    for (int i=1;i<=m;i++) {
        if (pdy[i]) continue;
        int gap=cx[x]+cy[i]-val[x][i];
        if (!gap) {
            pdy[i]=1;
            if (!belong[i]||dfs(belong[i])) {
                belong[i]=x;
                return true;
            }
        } else slack[i]=min(gap,slack[i]);
    }
    return false;
}
int km()
{
    memset(belong,0,sizeof(belong));
    memset(cy,0,sizeof(cy));
    for (int i=1;i<=n;i++) {
        cx[i]=val[i][1];
        for (int j=2;j<=m;j++) cx[i]=max(cx[i],val[i][j]);
    }
    for (int i=1;i<=n;i++) {
        for (int j=1;j<=m;j++) slack[j]=inf;
        while (true) {
            memset(pdx,0,sizeof(pdx));
            memset(pdy,0,sizeof(pdy));
            if (dfs(i)) break;
            int d=inf;
            for (int j=1;j<=m;j++)
             if (!pdy[j]) d=min(d,slack[j]);
            for (int j=1;j<=n;j++)
             if (pdx[j]) cx[j]-=d;
            for (int j=1;j<=m;j++)
             if (pdy[j]) cy[j]+=d;
             else slack[j]-=d;
        }
    }
    int ans=0;
    for (int i=1;i<=m;i++) ans+=val[belong[i]][i];
    return ans; 
}
int main()
{
    freopen("a.in","r",stdin);
    scanf("%d%d",&n,&m);
    for (int i=1;i<=m;i++) scanf("%d%d%d",&x[i],&y[i],&c[i]),mp[x[i]][y[i]]=i;
    for (int i=1;i<n;i++) {
        int u,v; scanf("%d%d",&u,&v);
        int t=mp[u][v]; mark[t]=1;
        add(x[t],y[t],c[t],i);
    }
    dfs(1,0); cnt=0;
    for (int i=1;i<=m;i++) 
    if (!mark[i]){
        ++cnt; check(i);
    }
    m=n+cnt; n=cnt;
    printf("%d\n",km());
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值