HDU 3311 :Dig The Wells(斯坦纳树模板)

在这里插入图片描述

题目大意:有 n 座庙,每个庙里有 n 个和尚,有 m 个其它地点,这 n + m n + m n+m 个地点都可以打井,在每个点打井有一个花费 a [ i ] a[i] a[i]。另外还有 p p p 条无向边,每条边有一个花费 w w w,求要使得这 n n n 个和尚都能喝到水的最小花费。


引入一个虚点0,连向其它所有点,边权为这些点的点权,则题目转化为 0号点为根节点,使得 前 n 个点连通的最小花费,套斯坦纳树模板。


代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e3 + 10;
#define pii pair<int,int>
#define fir first
#define sec second
vector<pii> g[maxn];
int n,m,p;
int val[maxn],st[maxn],endst;
queue<int> q;
int vis[maxn][1 << 7 + 1];
int dp[maxn][1 << 7 + 1];
void spfa(int state) {
	while(!q.empty()) {
		int top = q.front();
		q.pop();
		vis[top][state] = 0;
		for(auto it : g[top]) {
			int v = it.fir,w = it.sec;
			if(dp[v][st[v] | state] == -1 || dp[v][st[v] | state] > dp[top][state] + w) {
				dp[v][st[v] | state] = dp[top][state] + w;
				if((st[v] | state) != state || vis[v][state]) continue;
				q.push(v);
				vis[v][state] = 1;
			}
		}
	}
}
void steniertree() {
	for(int i = 0; i <= n + m; i++)
		dp[i][st[i]] = 0;
	for(int j = 1; j < endst; j++) {
		for(int i = 0; i <= n + m; i++) {
			if(st[i] && (st[i] & j) == 0) continue;
			for(int k = j & (j - 1); k; k = (k - 1) & j) {
				int x = st[i] | k,y = st[i] | (j - k);
				if(dp[i][x] != -1 && dp[i][y] != -1) {
					if(dp[i][j] == -1 || dp[i][x] + dp[i][y] < dp[i][j])
						dp[i][j] = dp[i][x] + dp[i][y];	
				}
			}
			if(dp[i][j] != -1) {
				q.push(i);
				vis[i][j] = 1;
			}
		}
		spfa(j);
	}
}
int main() {
	while(~scanf("%d%d%d",&n,&m,&p)) {		
		for(int i = 0; i <= n + m; i++)
			g[i].clear(),st[i] = 0;		
		for(int i = 1; i <= n + m; i++) 
			scanf("%d",&val[i]);
		for(int i = 0; i <= n; i++)
			st[i] = (1 << i);
		endst = 1 << (n + 1);		
		memset(dp,-1,sizeof dp);
		for(int i = 1; i <= p; i++) {
			int u,v,w;scanf("%d%d%d",&u,&v,&w);
			g[u].push_back(pii(v,w));
			g[v].push_back(pii(u,w));
		}
		for(int i = 1; i <= n + m; i++) {
			g[0].push_back(pii(i,val[i]));
			g[i].push_back(pii(0,val[i]));
		} 	
		steniertree();
		printf("%d\n",dp[0][endst - 1]);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值