有向图的最小生成树(最小树形图)hdu4009 2011大连赛区网络赛1009

/*
http://hi.baidu.com/bin183/blog/item/45c37950ece4475f1138c273.html
最小树形图(有向图的最小生成树)思想:对于有根的图,首先求出每个点费用最小的前驱边,如果这些前驱边构成了回路,
       那么缩点,同时以这个大点中的某个点为终点的边的权值减去这个点在圈中的前驱边的权值。不断重复这过程,直至没有回路。
    对于没有根的图,另加一个根,权值为所有权值的和,这样的话,与根相连的边只会选择一条,所以求的值再减去即可。
题意:有一些村庄,现要求使所有村庄都有水的最少费用。每个村庄可以通过挖井,从别的村庄拉水管的方式使村庄有水。
分析:因为每个村庄都可以挖井,所以没有不可能的情况。然后新加一个结点,表示挖井的费用,而后再根据输入构图。求最小树形图即可
因为数据较大,要用邻接表的实现。时间复杂度为O(V*E)
*/
#include<stdio.h>
#include<string.h>
#include<cmath>
#include<iostream>

const int maxn=1100;
const int maxm=1100000;
const int maxint=0x3fffffff;

struct edge
{
	int u,v,w;
	edge(){}
	edge(int u1,int v1,int w1):u(u1),v(v1),w(w1){}
}e[maxm];
int root,n,edgeNum,vis[maxn],pre[maxn],belong[maxn],in[maxn];
int a[maxn],b[maxn],c[maxn];
int Abs(int a)
{
	return a>0? a:-a;
}
int Dis(int i,int j)
{
	return Abs(a[i]-a[j])+Abs(b[i]-b[j])+Abs(c[i]-c[j]);
}

int solve()
{
	int i,j,k,num,sum=0;

	n++;//开始是0至n,n+1个点
	while(1)//不断缩点,直至不含圈
	{
		for(i=0;i<n;i++)
			in[i]=maxint;
		for(i=0;i<edgeNum;i++)//找每个点的前驱边
		{
			if(in[e[i].v]>e[i].w&&e[i].u!=e[i].v)
			{
				pre[e[i].v]=e[i].u;
				in[e[i].v]=e[i].w;
			}
		}

		memset(vis,-1,sizeof(vis));
		memset(belong,-1,sizeof(belong));
		in[root]=0;
		for(num=0,i=0;i<n;i++)
		{
			sum+=in[i];//加上每个点的前驱边
			j=i;
			while(vis[j]!=i&&belong[j]==-1&&j!=root)//判断是否有圈
			{
				vis[j]=i;
				j=pre[j];
			}
			if(vis[j]==i)//j一定在圈上,而i不一定
			{
				for(k=pre[j];k!=j;k=pre[k])//第num个圈,缩点
					belong[k]=num;
				belong[j]=num++;
			}
		}

		if(!num) return sum;//不含圈,则结束
		for(i=0;i<n;i++)
			if(belong[i]==-1)//不属于某个圈的是独立的点
				belong[i]=num++;

		for(i=0;i<edgeNum;i++)
		{
			int j=e[i].v;
			e[i].u=belong[e[i].u];
			e[i].v=belong[e[i].v];
			e[i].w-=in[j];//注意是减去缩点前的前驱边,而不是in[e[i].v]
		}
		n=num;
		root=belong[root];
	}
	return sum;
}
int main()
{
	int x,y,z,i,j,k,ii,d;
	while(scanf("%d%d%d%d",&n,&x,&y,&z)!=EOF)
	{
		if(!n&&!x&&!y&&!z) break;
		for(edgeNum=0,i=1;i<=n;i++)
		{
			scanf("%d%d%d",&a[i],&b[i],&c[i]);
			e[edgeNum++]=edge(0,i,c[i]*x);
		}
		for(i=1;i<=n;i++)
		{
			scanf("%d",&k);
			while(k--)
			{
				scanf("%d",&ii);
				if(i==ii) continue;
				d=Dis(i,ii);
				if(c[i]>=c[ii])//i向ii提供水
					e[edgeNum++]=edge(i,ii,d*y);
				else
					e[edgeNum++]=edge(i,ii,d*y+z);
			}
		}

		root=0;
		printf("%d\n",solve());
	}
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值