hdu 4009 Transfer water

一直写不出来,后来才知道用到的是最小树形图的算法,之前还以为是最小生成树或者最短路径的做法,所以一直做不出来,去百度了下最小生成树的一些东东~

最小树形图
  最小树形图,就是给有向带权图中指定一个特殊的点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(看这些文字好抽象的说~~~)用张图来说明下吧~

如图所示,当找到环之后,将环缩为一个顶点,取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;
}




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值