无源点最小树形图模板(4009)

Transfer water

Time Limit: 5000/3000 MS (Java/Others)    Memory Limit: 65768/65768 K (Java/Others)
Total Submission(s): 5039    Accepted Submission(s): 1820


Problem Description
XiaoA lives in a village. Last year flood rained the village. So they decide to move the whole village to the mountain nearby this year. There is no spring in the mountain, so each household could only dig a well or build a water line from other household. If the household decide to dig a well, the money for the well is the height of their house multiplies X dollar per meter. If the household decide to build a water line from other household, and if the height of which supply water is not lower than the one which get water, the money of one water line is the Manhattan distance of the two households multiplies Y dollar per meter. Or if the height of which supply water is lower than the one which get water, a water pump is needed except the water line. Z dollar should be paid for one water pump. In addition,therelation of the households must be considered. Some households may do not allow some other households build a water line from there house. Now given the 3‐dimensional position (a, b, c) of every household the c of which means height, can you calculate the minimal money the whole village need so that every household has water, or tell the leader if it can’t be done.
 

Input
Multiple cases.
First line of each case contains 4 integers n (1<=n<=1000), the number of the households, X (1<=X<=1000), Y (1<=Y<=1000), Z (1<=Z<=1000).
Each of the next n lines contains 3 integers a, b, c means the position of the i‐th households, none of them will exceeded 1000.
Then next n lines describe the relation between the households. The n+i+1‐th line describes the relation of the i‐th household. The line will begin with an integer k, and the next k integers are the household numbers that can build a water line from the i‐th household.
If n=X=Y=Z=0, the input ends, and no output for that.
 

Output
One integer in one line for each case, the minimal money the whole village need so that every household has water. If the plan does not exist, print “poor XiaoA” in one line.
 

Sample Input
  
  
2 10 20 30 1 3 2 2 4 1 1 2 2 1 2 0 0 0 0
 

Sample Output
  
  
30
Hint
In 3‐dimensional space Manhattan distance of point A (x1, y1, z1) and B(x2, y2, z2) is |x2‐x1|+|y2‐y1|+|z2‐z1|.
 

Source



首先分析题目知:整个图没有源点,挖井的点都是源点。所以需要建立一个虚根结点,使它连接所有点,边权值为挖井的费用,于是就可以从这个虚根结点跑一次朱刘算法即可。(注意建图的时候要把所有可能的有向边都加进去,所以要连接所有点)


求有固定根的最小树形图的算法

算法步骤:

(1)求最短弧集:除了根节点外,找到所有其他的节点最小边权的入边(用in数组记录到达改点的最小边权,用pre数组记录其父节点)

(2)检验生成的集合中是否存在有向圈,有的话进行步骤3,没有进行步骤4,假如除了根节点外有的节点是孤立的,也就是没有弧指向他,不存在最小树形图;

(判断方法:利用pre数组以每个点进行枚举搜索)

(3)把有向环缩成一个点,形成(U1,U2,U3,U4.....Un')共n‘个点定义:不在一个环中的两个点分别为id[u],id[v],边权是w[u][v]-=in[v];然后进行步骤1;

(4)ans就是答案:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <vector>

using namespace std;

#define N 1050
#define INF 0x3F3F3F3F

int n;

struct Point
{
	int x,y,z;
};
Point point[N];

struct Edge
{
	int from,to,dis;
};
vector<Edge> edges;

int dis(Point a,Point b) //曼哈顿距离
{
	return abs(a.x-b.x)+abs(a.y-b.y)+abs(a.z-b.z);
}

int in[N]; //in数组记录到达该点的最小边权
int pre[N]; //pre数组记录其父结点
int ha[N]; //对点进行编号,用这个编号区分这个点是否在一个环上
int vis[N]; //从某一点i逆向寻路中的每个点vis都标记为i,为ha数组服务
int zhuliu(int root)
{
	int ans=0; //保存最终结果
	while(true)
	{
		int edges_num=edges.size();
		memset(in,0x3F,sizeof(in));
		memset(pre,-1,sizeof(pre));

		for(int i=0;i<edges_num;i++) //找除根结点以外的最小入边,记录在pre数组和in数组中
		{
			int from=edges[i].from,to=edges[i].to;
			if(edges[i].dis<in[to]&&from!=to)
			{
				in[to]=edges[i].dis;
				pre[to]=from;
			}
		}
		in[root]=0;

		// for(int i=0;i<n;i++)
		// 	printf("%d ",in[i]);
		// printf("\n");

		for(int i=0;i<n;i++)
			if(i!=root&&in[i]==INF) //有不可达的点
				return -1;

		int cnt=0; //自环数量
		memset(ha,-1,sizeof(ha));
		memset(vis,-1,sizeof(vis));
		for(int i=0;i<n;i++) //遍历每一个点找是否存在自环
		{
			ans+=in[i];
			int v=i;
			//1.每个点寻找其前序点,要么最终寻找至根部,要么找到一个环
			//2.注意每个点仅一条入边了现在
			//3.不能碰到其它环,否则会混乱 
			while(v!=root&&ha[v]==-1&&vis[v]!=i)
			{
				vis[v]=i;
				v=pre[v];
			}
			if(v!=root&&ha[v]==-1) //当v不是根结点且没碰到其它环,即找到了一个环,于是进行缩点
			{
				for(int j=pre[v];j!=v;j=pre[j])
					ha[j]=cnt;
				ha[v]=cnt;
				cnt++;
			}
		}

		if(cnt==0) break; //当不存在自环,跳出循环

		for(int i=0;i<n;i++) //把余下的不在环里的点编号
			if(ha[i]==-1) ha[i]=cnt++;

		//建新图(注意这里是对原图进行操作!!!!!)
		for(int i=0;i<edges_num;i++) //遍历每一条边
		{
			int temp=edges[i].to;
            edges[i].from=ha[edges[i].from]; //缩点
            edges[i].to=ha[edges[i].to];
            if(edges[i].from!=edges[i].to) //当起点和终点不在一个环上时
                edges[i].dis-=in[temp]; //这一步很关键,画图思考,为了消除自环
                        //(所有对环上点的入边都要修改值,即对新结点的入边)
			//上一篇博客有解释为什么出边的权不变,入边的权要减去in[i]
			//如果新图中e的权w'(e)是原图中e的权w(e)减去in[u]权的话,那么在我们删除掉in[u],
			//并且将e恢复为原图状态的时候,这个树形图的权仍然是新图树形图的权加环的权,
			//而这个权值正是最小树形图的权值
		}

		n=cnt; //更新结点数和根结点的编号
		root=ha[root];
	}
	return ans;
}

int main()
{
    //freopen("input.txt","r",stdin);
    int x,y,z;
    while(scanf("%d%d%d%d",&n,&x,&y,&z)==4)
    {
    	if(n==0&&x==0&&y==0&&z==0) break;
    	for(int i=0;i<n;i++)
    		scanf("%d%d%d",&point[i].x,&point[i].y,&point[i].z);
    	for(int i=0;i<n;i++) //把所有边保存下来(所有可能的边都要加进去)
    	{
    		int k,x;
    		scanf("%d",&k);
    		while(k--)
    		{
    			scanf("%d",&x); //从i到x-1的有向边
    			x--;
    			if(x==i) continue;
    			int temp=dis(point[i],point[x])*y; //边权值的计算
    			if(point[i].z<point[x].z) temp+=z;

    			edges.push_back((Edge){i,x,temp});
    		}
    	}
    	n++;
    	//点范围0~n-1
    	//增加一个源点(序号为n-1),使这个源点连接所有点,边权值为挖井的花费
    	for(int i=0;i<n-1;i++)
    		edges.push_back((Edge){n-1,i,point[i].z*x});

    	// int edges_num=edges.size();
    	// for(int i=0;i<edges_num;i++)
    	// 	printf("%d %d %d\n",edges[i].from,edges[i].to,edges[i].dis);

    	printf("%d\n",zhuliu(n-1));

    	edges.clear(); //别忘记清空
    }
    return 0;
}





  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值