bzoj1937 [Shoi2004]Mst 最小生成树(KM)

Description
这里写图片描述

Input
第一行为N、M,其中 表示顶点的数目, 表示边的数目。顶点的编号为1、2、3、……、N-1、N。接下来的M行,每行三个整数Ui,Vi,Wi,表示顶点Ui与Vi之间有一条边,其权值为Wi。所有的边在输入中会且仅会出现一次。再接着N-1行,每行两个整数Xi、Yi,表示顶点Xi与Yi之间的边是T的一条边。

Output
输出最小权值

Sample Input
6 9
1 2 2
1 3 2
2 3 3
3 4 3
1 5 1
2 6 3
4 5 4
4 6 7
5 6 6
1 3
2 3
3 4
4 5
4 6

Sample Output
8

【样例说明】
边(4,6)的权由7修改为3,代价为4
边(1,2)的权由2修改为3,代价为1
边(1,5)的权由1修改为4,代价为3
所以总代价为4+1+3=8
修改方案不唯一

HINT
1<=n<=50,1<=m<=800,1<=wi<=1000
n–>点数..m–>边数..wi—>边权

分析:
我一开始十分simple的想:
我们可以直接在图上作最小生成树,得到一个ans,
实际上这就是我们修改的目标,那我们直接跑一个最小生成树不就行了嘛

然而我忽略了一个条件:
我们可以修改E中的任意一条边

于是我就懵了
实际上,根据贪心的思想,原本在T中的边只减不增非T边只增不减
而最小生成树又有一个的性质:
对于任意一条树边i和任意一条非树边j,一定有
Wi-di<=Wj+dj (改变后的树边权值一定小于等于任何一条非树边)

看似是一个很zz很显然的结论
但是我们稍微变一下形,就可以得到:di+dj>=Wi-Wj
如果我们把d看做一个函数
那么这和可行顶标的定义是一样的:

可行顶标:
可行顶标是一个节点函数l,使得对于任意的弧(x,y),都有l(x)+l(y)>=W(x,y)

相等子图:
包含原图的所有点,只包含l(x)+l(y)=W(x,y)的边
相等子图的完美匹配就是原图的最大权匹配

也就是说我们只要找到一个可行相等子图,
那么完美匹配的边权和就是di的和

我们把所有边都看成点
X部:非树边
Y部:树边
两部之间的连边是边权差(di+dj>=Wi-Wj( 树边-非树边))
直接跑(最小权的)KM即可

注意:

由于非树边和树边可能数量不同
而KM是最大权完美匹配,所以两边的点至少有一排应该是全部匹配上的,所以我们需要在树边(Y部)添加一些虚点
用来对应X部的点
这些虚点连出来的边都是0

在程序中有一个dfs树边的过程,这是方便在处理每个非树边的时候,可以一口气找到所有和非树边两端点连接的所有树边,便于计算边权

tip

好好读题
不能光会模板,概念原理证明都要通晓
板子要记扎实!!!

//这里写代码片
#include<cstdio>
#include<cstring>
#include<iostream>

using namespace std;

const int INF=0x33333333;
const int N=1000;
int n,m,cnt;
struct node{
    int x,y,z;
};
node e[N];
struct node2{
    int x,y,z,nxt,num;
};
node2 way[N];
int mp[55][55],st[N],tot=0,fa[N],cur[N],deep[N];
int Lx[N],Ly[N],slack[N],W[N][N],belong[N];
bool pp[N],L[N],R[N];

void add(int u,int w,int z,int k)
{
    tot++;
    way[tot].x=u;way[tot].y=w;way[tot].z=z;way[tot].num=k;way[tot].nxt=st[u];st[u]=tot;
    tot++;
    way[tot].x=w;way[tot].y=u;way[tot].z=z;way[tot].num=k;way[tot].nxt=st[w];st[w]=tot;
}

void dfs(int now,int f)
{
    deep[now]=deep[f]+1;
    for (int i=st[now];i;i=way[i].nxt)
        if (way[i].y!=f)
        {
            fa[way[i].y]=now;
            cur[way[i].y]=i;   //和y相连的树边编号 
            dfs(way[i].y,now);
        }
}

void build(int i)   //i是非树边 
{
    int xx=e[i].x;
    int yy=e[i].y;
    while (xx!=yy)
    {
        if (deep[xx]>deep[yy])
        {
            int t=cur[xx];   //cur记录和xx相连的树边 
            W[cnt][way[t].num]=way[t].z-e[i].z;   //num是边的编号,cut是当前非树边在X部中的编号 
            xx=fa[xx];
        }
        else
        {
            int t=cur[yy];
            W[cnt][way[t].num]=way[t].z-e[i].z;
            yy=fa[yy];
        }
    }
    W[cnt][n+cnt]=0;
}

int match(int i)
{
    L[i]=1;
    for (int j=1;j<=m;j++)
        if (!R[j])
        {
            int v=Lx[i]+Ly[j]-W[i][j];
            if (!v)
            {
                R[j]=1;
                if (!belong[j]||match(belong[j]))
                {
                    belong[j]=i;
                    return 1;
                }
            }
            else slack[j]=min(slack[j],v);
        }
    return 0;
}

int KM()
{
    memset(Ly,0,sizeof(Ly));
    memset(belong,0,sizeof(belong));
    for (int i=1;i<=n;i++)
    {
        Lx[i]=W[i][1];
        for (int j=2;j<=m;j++)
            Lx[i]=max(Lx[i],W[i][j]);
    }
    for (int i=1;i<=n;i++)
    {
        for (int j=1;j<=m;j++) slack[j]=INF;
        while (1)
        {
            memset(L,0,sizeof(L));
            memset(R,0,sizeof(R));
            if (match(i)) break;
            int a=INF;
            for (int j=1;j<=m;j++)
                if (!R[j]) a=min(a,slack[j]);
            for (int j=1;j<=n;j++)
                if (L[j]) Lx[j]-=a;
            for (int j=1;j<=m;j++)
                if (R[j]) Ly[j]+=a;
                else slack[j]-=a;
        }
    }
    int ans=0;
    for (int i=1;i<=m;i++) ans+=W[belong[i]][i];
    return ans;
}

int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=m;i++)
        scanf("%d%d%d",&e[i].x,&e[i].y,&e[i].z),mp[e[i].x][e[i].y]=i;
    for (int i=1;i<n;i++)
    {
        int u,w;
        scanf("%d%d",&u,&w);
        int t=mp[u][w];   //边的编号 
        pp[t]=1;   //树边 
        add(e[t].x,e[t].y,e[t].z,i);
    }
    dfs(1,0);
    cnt=0;
    for (int i=1;i<=m;i++)
        if (!pp[i])   //非树边 
        {
            cnt++;build(i);
        }
    m=n+cnt; n=cnt;
    printf("%d
",KM());
}
原文地址:https://www.cnblogs.com/wutongtong3117/p/7673109.html