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树边的过程,这是方便在处理每个非树边的时候,可以一口气找到所有和非树边两端点连接的所有树边,便于计算边权
不能光会模板,概念原理证明都要通晓
板子要记扎实!!!
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()); }
相关文章推荐
- 【KM】BZOJ1937 [Shoi2004]Mst 最小生成树
- [BZOJ1937][Shoi2004]Mst 最小生成树(KM)
- [BZOJ1937][Shoi2004]Mst 最小生成树(KM)
- bzoj 1937: [Shoi2004]Mst 最小生成树
- 【BZOJ1937】[Shoi2004]Mst 最小生成树 KM算法(线性规划)
- BZOJ 1937: [Shoi2004]Mst 最小生成树 [二分图最大权匹配]
- bzoj 1937: [Shoi2004]Mst 最小生成树 费用流
- 【bzoj1937】[Shoi2004]Mst 最小生成树
- [对偶 KM算法 生成树 || 最大费用可行流 || 线性规划] BZOJ 1937 [Shoi2004]Mst 最小生成树
- bzoj1937[Shoi2004]Mst 最小生成树
- [BZOJ 1937][Shoi2004]Mst 最小生成树
- BZOJ 1937[Shoi2004]Mst 最小生成树
- bzoj 1937: [Shoi2004]Mst 最小生成树 (KM算法)
- bzoj 1937: [Shoi2004]Mst 最小生成树
- [BZOJ1937][SHOI2004]Mst最小生成树(KM算法,最大费用流)
- Bzoj1937 [Shoi2004]Mst 最小生成树
- bzoj 1937: [Shoi2004]Mst 最小生成树
- Bzoj1937:[Shoi2004]Mst 最小生成树:KM算法
- 【bzoj1937】 Shoi2004—Mst 最小生成树
- bzoj 1937: [Shoi2004]Mst 最小生成树 KM算法