zoukankan      html  css  js  c++  java
  • 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());
    }
  • 相关阅读:
    30个实例详解TOP命令
    CentOS6 启动流程图文解剖
    Linux常用性能检测命令解释
    Linux 多核下绑定硬件中断到不同 CPU(IRQ Affinity) 转
    linux系统中如何查看日志 (常用命令2)
    Linux下常用的shell命令记录1
    python import as from
    内存的大小端模式
    opencv笔记1
    代码不仅仅考虑性能
  • 原文地址:https://www.cnblogs.com/wutongtong3117/p/7673108.html
Copyright © 2011-2022 走看看