P6192 【模板】最小斯坦纳树

Description

给定一张 n n n m m m 边的无向连通图,其中有 k k k 个点是关键点。求图的一个子图,满足包含了所有 k k k 个关键点,使得所包含的边集的权值和最小,求这个最小值。

Analysis

首先,答案子图肯定是一棵树,原因显然。
由于 k k k 很小,考虑状压dp,设 d p u , S dp_{u,S} dpu,S 表示以 u u u 为根,关键点集合为 S S S 时的答案,则有两种情况:

  • u u u 的度数为 1 1 1,则可以删除 u u u,找一个相邻的节点 v v v 来替代,有 d p u , S = d p v , S + w ( u , v ) dp_{u,S}=dp_{v,S}+w(u,v) dpu,S=dpv,S+w(u,v),这里我们用 SPFA 跑最短路实现转移(这里用 SPFA 优于 Dijkstra).
  • u u u 的度数大于 1 1 1,可以将 S S S 分成两个集合 A , B A,B A,B,有: d p u , S = d p u , A + d p u , B dp_{u,S}=dp_{u,A}+dp_{u,B} dpu,S=dpu,A+dpu,B,可以通过枚举子集实现转移。

Code

// Problem: P6192 【模板】最小斯坦纳树
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P6192
// Memory Limit: 250 MB
// Time Limit: 1000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include <iostream>
#include <queue>
using namespace std;
#define int long long
using PII = pair<int, int>;
const int INF = 1e18;

signed main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	
	int n, m, k;
	cin >> n >> m >> k;
	
	vector<vector<PII>> G(n);
	for(int i = 0, u, v, w; i < m; i++){
		cin >> u >> v >> w;
		u--; v--;
		
		G[u].emplace_back(v, w);
		G[v].emplace_back(u, w);
	}
	
	int up = 1 << k;
	vector<vector<int>> dp(n, vector<int>(up, INF));
	vector<int> p(k);
	for(int i = 0; i < k; i++){
		cin >> p[i];
		p[i]--;
		dp[p[i]][1 << i] = 0;
	}
	
	queue<int> q;
	vector<bool> vis(n, false);
	
	auto spfa = [&](int state){
		while(q.size()){
			int u = q.front();
			q.pop();
			vis[u] = false;
			
			for(auto edge: G[u]){
				int v = edge.first, w = edge.second;
				if(dp[v][state] > dp[u][state] + w){
					dp[v][state] = dp[u][state] + w;
					if(!vis[v]){
						q.push(v);
						vis[v] = true;
					}
				}
			}
		}
	};
	
	for(int s1 = 1; s1 < up; s1++){
		for(int i = 0; i < n; i++){
			for(int s2 = s1 & (s2 - 1); s2; s2 = s1 & (s2 - 1))
			    dp[i][s1] = min(dp[i][s1], dp[i][s2] + dp[i][s1 ^ s2]);
			if(dp[i][s1] != INF){
				q.push(i);
				vis[i] = true;
			}
		}
		spfa(s1);
	}
	cout << dp[p[0]][up - 1] << endl;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值