您的位置:首页 > 其它

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

2017-10-01 17:25 351 查看
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
;
struct node2{
int x,y,z,nxt,num;
};
node2 way
;
int mp[55][55],st
,tot=0,fa
,cur
,deep
;
int Lx
,Ly
,slack
,W

,belong
;
bool pp
,L
,R
;

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\n",KM());
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: