斯坦那树学习笔记

引子

最小斯坦纳树
大概意思就是一个图给出 \(k(k \le 10)\) 个关键点,要求选出若干条边使得这 \(k\) 个关键点连通,求边权和的最小值

\(Analysis\)

发现 \(k\) 很小,考虑状压 \(dp\)
为得到最优解,我们需要考虑以每个点为根的形态
\(f_{i,S}\) 表示以 \(i\) 为根关键点的状态为 \(S\) 时的最优解
那么两个转移
一、 \(f_{i,S} = \min(f_{i,S},f_{i,S0}+f_{i,S1})\)
表示用 \(i\) 这个根将两个不相交的状态直接合并在一起
二、 \(f_{i,S} = \min(f_{i,S},f_{k,S}+e(k,i))\)
表示用之前的一个以 \(k\) 为根的状态连一条边到 \(i\) 变为以 \(i\) 为根
我们发现第二种转移无法确定最佳的 \(i,k\) 的顺序,即 \(dp\) 有后效性
这是考虑用最短路算法转移即可

\(Code\)

#include<cstdio>
#include<queue>
#include<cstring>
#include<iostream>
using namespace std;

const int N = 105, INF = 0x3f3f3f3f;
int n, m, k, g[N][N], key[N], h[N], f[N][1045], vis[N];

struct edge{
	int to, nxt, w;
}e[N * 10];
inline void add(int u, int v, int w)
{
	static int tot = 0;
	e[++tot] = edge{v, h[u], w}, h[u] = tot;
}

queue<int> Q;
inline void spfa(int s)
{
	while (!Q.empty())
	{
		int now = Q.front(); Q.pop();
		for(int i = h[now]; i; i = e[i].nxt)
		if (f[e[i].to][s] > f[now][s] + e[i].w)
		{
			f[e[i].to][s] = f[now][s] + e[i].w;
			if (!vis[e[i].to]) vis[e[i].to] = 1, Q.push(e[i].to);
		}
		vis[now] = 0;
	}
}
inline void Stenir_Tree()
{
	for(int s = 1; s < (1 << k); s++)
	{
		memset(vis, 0, sizeof vis);
		for(int i = 1; i <= n; i++)
		{
			for(int t = (s - 1) & s; t; t = (t - 1) & s) 
				f[i][s] = min(f[i][s], f[i][t] + f[i][s ^ t]);
			if (f[i][s] != INF) vis[i] = 1, Q.push(i);
		}
		spfa(s);
	}
}

int main()
{
	memset(g, INF, sizeof g), memset(f, INF, sizeof f);
	scanf("%d%d%d", &n, &m, &k);
	for(int i = 1, x, y, z; i <= m; i++) 
		scanf("%d%d%d", &x, &y, &z), g[x][y] = g[y][x] = min(g[x][y], z);
	for(int i = 1; i <= n; i++)
		for(int j = i + 1; j <= n; j++)
		if (g[i][j] != INF) add(i, j, g[i][j]), add(j, i, g[i][j]);
	for(int i = 0; i < k; i++) scanf("%d", &key[i]), f[key[i]][1 << i] = 0;
	Stenir_Tree();
	int ans = INF;
	for(int i = 1; i <= n; i++) ans = min(ans, f[i][(1 << k) - 1]);
	printf("%d\n", ans);
}
posted @ 2021-07-07 20:51  leiyuanze  阅读(39)  评论(0编辑  收藏  举报