【BZOJ1937】[Shoi2004]Mst 最小生成树 KM算法(线性规划)

【BZOJ1937】[Shoi2004]Mst 最小生成树

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--->边权

题解:神题~

显然,树边的权值一定减小,非树边的权值一定增大,所以如果非树边j覆盖了树边i,则有wj+dj>=wi-di,即di+dj>=wi-wj。所以这。。。tm是KM算法中的顶标?

复习KM的原理,KM算法就是始终满足:对于每条边a-b,l(a)+l(b)>=v(a,b),并且所有l(a)+l(b)=v(a,b)的边构成的子图叫相等子图。并在满足上述条件下不断调整定标,使得相等子图不断扩大。

而对于本题,让wi-wj就是边权,然后求出最优匹配既是答案。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
int n,m,Cnt,nm,ans;
int map[60][60],pa[810],pb[810],pc[810],To[110],Next[110],Val[110],Head[60],len[60];
int fa[60],dep[60],bel[60],la[60],lb[810],va[60],vb[810],from[810],v[60][810];
inline void Add(int a,int b,int c)
{
    To[Cnt]=b,Val[Cnt]=c,Next[Cnt]=Head[a],Head[a]=Cnt++;
}
inline int rd()
{
    int ret=0,f=1;  char gc=getchar();
    while(gc<'0'||gc>'9') {if(gc=='-')f=-f;   gc=getchar();}
    while(gc>='0'&&gc<='9')   ret=ret*10+gc-'0',gc=getchar();
    return ret*f;
}
void Dfs(int x)
{
    for(int i=Head[x];i!=-1;i=Next[i])  if(To[i]!=fa[x])    fa[To[i]]=x,dep[To[i]]=dep[x]+1,bel[To[i]]=Val[i],Dfs(To[i]);
}
void build(int a,int b,int c)
{
    if(dep[a]<dep[b])    swap(a,b);
    while(dep[a]>dep[b]) v[bel[a]][nm]=max(0,len[bel[a]]-c),a=fa[a];
    while(a!=b) v[bel[a]][nm]=max(0,len[bel[a]]-c),v[bel[b]][nm]=max(0,len[bel[b]]-c),a=fa[a],b=fa[b];
}
int dfs(int x)
{
    va[x]=1;
    for(int y=1;y<=nm;y++)   if(!vb[y]&&la[x]+lb[y]==v[x][y])
    {
        vb[y]=1;
        if(!from[y]||dfs(from[y]))
        {
            from[y]=x;
            return 1;
        }
    }
    return 0;
}
int main()
{
    n=rd(),m=rd();
    int i,j,k,a,b;
    memset(Head,-1,sizeof(Head));
    for(i=1;i<=m;i++)    pa[i]=rd(),pb[i]=rd(),pc[i]=rd(),map[pa[i]][pb[i]]=map[pb[i]][pa[i]]=i;
    for(i=1;i<n;i++)
    {
        a=rd(),b=rd(),Add(a,b,i),Add(b,a,i),len[i]=pc[map[a][b]],map[a][b]=map[b][a]=0;
    }
    dep[1]=1,Dfs(1);
    for(i=1;i<=m;i++)    if(map[pa[i]][pb[i]])
    {
        nm++;
        build(pa[i],pb[i],pc[i]);
    }
    for(i=1;i<=n;i++)    for(j=1;j<=nm;j++)   la[i]=max(la[i],v[i][j]);
    for(i=1;i<=n;i++)
    {
        while(1)
        {
            memset(va,0,sizeof(va)),memset(vb,0,sizeof(vb));
            if(dfs(i))  break;
            int tmp=1<<30;
            for(j=1;j<=n;j++)    if(va[j])   for(k=1;k<=nm;k++)   if(!vb[k])  tmp=min(tmp,la[j]+lb[k]-v[j][k]);
            if(tmp==1<<30)    break;
            for(j=1;j<=n;j++)    if(va[j])   la[j]-=tmp;
            for(j=1;j<=nm;j++)   if(vb[j])   lb[j]+=tmp;
        }
    }
    for(i=1;i<=n;i++)    ans+=la[i];
    for(i=1;i<=nm;i++)   ans+=lb[i];
    printf("%d",ans);
    return 0;
}
posted @   CQzhangyu  阅读(939)  评论(0编辑  收藏  举报
编辑推荐:
· DeepSeek 解答了困扰我五年的技术问题
· 为什么说在企业级应用开发中,后端往往是效率杀手?
· 用 C# 插值字符串处理器写一个 sscanf
· Java 中堆内存和栈内存上的数据分布和特点
· 开发中对象命名的一点思考
阅读排行:
· PPT革命!DeepSeek+Kimi=N小时工作5分钟完成?
· What?废柴, 还在本地部署DeepSeek吗?Are you kidding?
· DeepSeek企业级部署实战指南:从服务器选型到Dify私有化落地
· 程序员转型AI:行业分析
· 重磅发布!DeepSeek 微调秘籍揭秘,一键解锁升级版全家桶,AI 玩家必备神器!
点击右上角即可分享
微信分享提示