hdu 3311 Dig The Wells (SteinerTree斯坦纳树)

Problem Description

You may all know the famousstory “Three monks”. Recently they find some places around their temples canbeen used to dig some wells. It will help them save a lot of time. But to digthe well or build the road to transport the water will cost money. They do notwant to cost too much money. Now they want you to find a cheapest plan.

 

Input

There are several test cases.
Each test case will starts with three numbers n , m, and p in one line, nstands for the number of monks and m stands for the number of places that canbeen used, p stands for the number of roads between these places. The placesthe monks stay is signed from 1 to n then the other m places are signed as n +1 to n + m. (1 <= n <= 5, 0 <= m <= 1000, 0 <=p <= 5000)
Then n + m numbers followed which stands for the value of digging a well in theith place.
Then p lines followed. Each line will contains three numbers a, b, and c. meansbuild a road between a and b will cost c.

 

Output

For each case, output theminimum result you can get in one line.

 

Sample Input

3 1 3

1 2 3 4

1 4 2

2 4 2

3 4 4

4 1 4

5 5 5 5 1

1 5 1

2 5 1

3 5 1

4 5 1

 

Sample Output

6

5

题目大意:选择一些点使得1~n的点与井直接或间接相连,使得总花费最小。1~n+m号点都可以成为井。每个点成为井的花费不同,且修每条路的花费也不同。

方法:由于能力值过渣,刚开始觉得是费用流……但是建图的时候处理不了每条路的花费。查了查才知道是一个叫做SteinerTree(斯坦纳树)的东西。

就百度了好久,看了各种资料。感觉这篇讲得不错http://blog.renren.com/share/327827934/14113666814?from=0101010202&ref=minifeed&sfet=102&fin=1&ff_id=327827934。以及这里面的资料讲得也挺好的http://download.csdn.net/detail/huang1196/668917。(本来想传下载下来传一个免费的,还是算了,太不厚道了)。

搞懂了斯坦纳树,这道题就基本上能够解决了。有一个处理就是,每个点成为井的花费怎么处理。如果直接每次SPFA的时候都加上的话,显然会被重复计算。由于开始想到的是费用流,所以自己在处理这个问题的时候就没有挣扎了。建立一个源点0,连接各点,而与各点之间的路程的花费就是各点成为井的花费。这样就能很好处理被重复计算的问题了。

 

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<queue>
#include<algorithm>
#define INF 999999999
#define MAX 1010
#define MAX_STATUS 1<<6
using namespace std;

struct EDGE
{
	int v ,w;
	int next;
}edges[MAX*13];

struct NODE
{
	int x ,y;
};

int head[MAX] ,cnt;

int n ,m ,p;
int value[MAX];

int status;//表示0~n号节点都被选择时的状态+1
int dis[MAX][MAX_STATUS] ,situation[MAX] ,vis[MAX][MAX_STATUS] ,dp;
//dis[i][j]表示以i节点为根选择点集状态为j时的最小值;situation[i]表示i节点对应的状态;vis[i][j]表示i节点为点集j时是否在队列中
queue<NODE> q;

void init()
{
	memset(head,-1,sizeof(head));
	memset(vis,0,sizeof(vis));
	memset(situation,0,sizeof(situation));
	cnt = 0;
	status = 1<<(n + 1);
	for(int i = 0;i <= n + m;i++)
	{
		for(int j = 0;j <= status;j++)
		{
			dis[i][j] = INF;
		}
	}
	for(int i = 0;i <= n;i++)
	{
		situation[i] = 1<<i;
		dis[i][situation[i]] = 0;
	}
}

void add_edges(int u ,int v ,int w)
{
	edges[cnt].v = v;
	edges[cnt].w = w;
	edges[cnt].next = head[u];
	head[u] = cnt;
	cnt++;

	edges[cnt].v = u;
	edges[cnt].w = w;
	edges[cnt].next = head[v];
	head[v] = cnt;
	cnt++;
}

void SPFA()
{
	NODE temp ,newd;
	while(!q.empty())
	{
		temp = q.front();
		q.pop();
		vis[temp.x][temp.y] = 0;
		for(int i = head[temp.x];i!=-1;i = edges[i].next)
		{
			int v ,situ;
			v = edges[i].v;
			situ = temp.y | situation[v];
			if(dis[temp.x][temp.y] + edges[i].w < dis[v][situ])
			{
				 dis[v][situ] = dis[temp.x][temp.y] + edges[i].w;
				 if(situ==temp.y && !vis[v][situ])
				 {
					 newd.x = v;
					 newd.y = situ;
					 q.push(newd);
					 vis[v][situ] = 1;
				 }
			}
		}
	}
}

void Steiner_Tree()
{
	NODE  temp;
	for(int i = 0;i < status;i++)
	{
		for(int j = 0;j <= n + m;j++)
		{
			for(int k = i;k;k = (k - 1) & i)
			{
				dis[j][i] = min(dis[j][i],dis[j][k|situation[j]]+dis[j][(i-k)|situation[j]]);
			}
			if(dis[j][i]!=INF)
			{
				temp.x = j;
				temp.y = i;
				q.push(temp);
				vis[j][i] = 1;
			}
		}
		SPFA();
	}
}

int DP()
{
	dp = INF;
	for(int j = 0;j <= n+m;j++)
	{
	        dp = min(dp,dis[j][status-1]);
	}
    return dp;
}

int main()
{
	int u ,v ,w;
	while(~scanf("%d%d%d",&n,&m,&p))
	{
		init();
		for(int i = 1;i <= m + n;i++)
		{
			scanf("%d",&value[i]);
			add_edges(0,i,value[i]);
		}
		for(int i = 0;i < p;i++)
		{
			scanf("%d%d%d",&u,&v,&w);
			add_edges(u,v,w);
		}
		Steiner_Tree();
		printf("%d\n",DP());
	}
	return 0;
}


 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值