HDU 3311 stainer树

//http://blog.renren.com/share/327827934/14113666814?from=0101010202&ref=minifeed&sfet=102&fin=1&ff_id=327827934
//搞懂了斯坦纳树,这道题就基本上能够解决了。有一个处理就是,每个点成为井的花费怎么处理。如果直接每次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;
	}//i为根;
}

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];///状态加上v点的状态。
			if(dis[temp.x][temp.y] + edges[i].w < dis[v][situ])///以i为跟,有状态j的点集的最小权值
			{
				 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)///(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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值