一直写不出来,后来才知道用到的是最小树形图的算法,之前还以为是最小生成树或者最短路径的做法,所以一直做不出来,去百度了下最小生成树的一些东东~
最小树形图
最小树形图,就是给有向带权图中指定一个特殊的点root,求一棵以root为根的有向生成树T,并且T中所有边的总权值最小。最小树形图的第一个算法是1965年朱永津和刘振宏提出的复杂度为O(VE)的算法。
判断是否存在树形图的方法很简单,只需要以v为根作一次图的遍历就可以了,所以下面的算法中不再考虑树形图不存在的情况。(首先得是一棵树,即起点与终点不能相同)
在所有操作开始之前,我们需要把图中所有的自环全都清除(比如2到2,3到3这样的自环)。很明显,自环是不可能在任何一个树形图上的。只有进行了这步操作,总算法复杂度才真正能保证是O(VE)。
****(做法)首先为除根之外的每个点选定一条入边,这条入边一定要是所有入边中最小(指向该点的边权最小)的。现在所有的最小入边都选择出来了,如果这个入边集不存在有向环的话,我们可以证明这个集合就是该图的最小树形图。这个证明并不是很难。如果存在有向环的话,我们就要将这个有向环缩成一个人工顶点,同时改变图中边的权。假设某点u在该环上,并设这个环中指向u的边权是in[u],那么对于每条从u出发的边(u, i, w),在新图中连接(new, i, w)的边,其中new为新加的人工顶点; 对于每条进入u的边(i, u, w),在新图中建立边(i, new, w-in[u])的边。为什么入边的权要减去in[u],这个后面会解释,在这里先给出算法的步骤。然后可以证明,新图中最小树形图的权加上旧图中被收缩的那个环的权和,就是原图中最小树形图的权。PS(看这些文字好抽象的说~~~)用张图来说明下吧~
![](https://img-my.csdn.net/uploads/201208/13/1344821626_4457.png)
如图所示,当找到环之后,将环缩为一个顶点,取V2和V3为例,V2的最小入边是7,而环指向V2的a1的权值就应该变成是9-7=2;即环外指向环内某一顶点的权值减去该顶点的最小入边即可。反复操作,直到新形成的图没有环,此时的新图就是最小树形图了。
这个就是我理解的最小树形图的大意,,,具体证明什么的,其实真心不会,以下就贴出hdu 4009的代码,,是较精短的模板~~
题意:有n个地方需要供水,每个地方都可以选择是自己挖井,还是从别的地方引水,根据方法不同和每个地方的坐标不同,花费也不同,现在给出每个地方的坐标,花费的计算方法,以及每个地方可以给哪些地方供水(即对方可以从这里引水),求给所有地方供水的最小花费。
思路:显然对于每个地方,只有一种供水方式就足够了,这样也能保证花费最小,而每个地方都可以自己挖井,所以是不可能出现无解的情况的,为了方便思考,我们引入一个虚拟点,把所有自己挖井的都连到这个点,边权为挖井的花费,而如果i能从j处引水,则从j向i连边,边权为引水的花费,然后对这个有向图,以虚拟点为根,求最小树形图即可(最小树形图即为有向图的最小生成树)。
#include <iostream>
#include <stdio.h>
#include <math.h>
#include <algorithm>
#define INF 0xffffff
#define MAXN 1005
using namespace std;
struct node
{
int st, end;
int val;
}map[MAXN * MAXN];
struct Point
{
int x,y,z;
}dina[MAXN];
int pre[MAXN], id[MAXN], vis[MAXN], n, m, pos;
int in[MAXN];
int X,Y,Z;
int Directed_MST(int root, int V, int E) //root 代表根节点,V代表点数,E代表边数
{
int ret = 0;
int i;
while(true)
{
//1.找最小入边
for( i = 0; i < V; i++)
in[i] = INF;
for( i = 0; i < E; i++)
{
int st = map[i].st;
int en = map[i].end;
if(map[i].val < in[en] && st != en) //去自环
{
pre[en] = st;
in[en] = map[i].val;
if(st == root)
pos = i;
}
}
for(i = 0; i < V; i++)
{
if(i == root)
continue;
if(in[i] == INF)
return -1;//除了根以外有点没有入边,则根无法到达它
}
//2.找环
int cnt = 0;
memset(id, -1, sizeof(id));
memset(vis, -1, sizeof(vis));
in[root] = 0;
for(i = 0; i < V; i++) //标记每个环
{
ret += in[i];
int en = i;
while(vis[en] != i && id[en] == -1 && en != root)
{
vis[en] = i;
en = pre[en];
}
if(en != root && id[en] == -1)
{
for(int st = pre[en]; st != en; st = pre[st])
id[st] = cnt;
id[en] = cnt++;
}
}
if(cnt == 0)
break; //无环 则break
for( i = 0; i < V; i++)
if(id[i] == -1)
id[i] = cnt++;
//3.建立新图
for(i = 0; i < E; i++)
{
int st = map[i].st;
int en = map[i].end;
map[i].st = id[st];
map[i].end = id[en];
if(id[st] != id[en])
map[i].val -= in[en];
}
V = cnt;
root = id[root];
}
return ret;
}
int judge(Point ans1,Point ans2)
{
int ans=abs(ans2.y-ans1.y)+abs(ans2.x-ans1.x)+abs(ans2.z-ans1.z);
return (ans);
}
int main()
{
int y,k;
while(~scanf("%d%d%d%d", &n,&X,&Y,&Z),n+X+Y+Z)
{
int i;
m=0;
for(i = 1; i<= n;i++)
{
scanf("%d%d%d",&dina[i].x,&dina[i].y,&dina[i].z);
}
int j=1;
for(i=1;i<=n;i++)
{
scanf("%d",&k);
m+=k;
for(; j <= m; j++)
{
scanf("%d", &y);
map[j].st=i;
map[j].end=y;
if(i!=y)
{
int ans=judge(dina[i],dina[y])*Y;
if(dina[y].z>dina[i].z)
ans+=Z;
map[j].val=ans;
}
else
{
map[j].val=INF;
}
}
}
for(i=1;j<=m+n;j++,i++)
{
map[j].st=0;
map[j].end=i;
map[j].val=dina[i].z*X;
}
int ans = Directed_MST(0, n+1 , m+n+1 );
if(ans == -1)
puts("poor XiaoA");
else
printf("%d\n",ans);
}
return 0;
}