搜索
您的当前位置:首页正文

最小斯坦纳树【模板】

来源:易榕旅网

>Link


>Description

在一张大小为 n n n 边数为 m m m 的无向图中,要求用最小的代价联通指定的 k k k 个点。

1 ≤ n ≤ 100 ,    1 ≤ m ≤ 500 ,    1 ≤ k ≤ 10 ,    1 ≤ u , v ≤ n ,    1 ≤ w ≤ 1 0 6 1\leq n\leq 100,\ \ 1\leq m\leq 500,\ \ 1\leq k\leq 10,\ \ 1\leq u,v\leq n,\ \ 1\leq w\leq 10^6 1n100,  1m500,  1k10,  1u,vn,  1w106


>解题思路

这是一道斯坦纳树模板题。

可以很容易得出,最终以最优代价联通 k k k 个点的子图是一棵树(边数最少)
看到 k k k 小于10,可以想到状压。我们设 f i , s f_{i,s} fi,s 为联通点集为 s s s,联通图(树)以 i i i 为根,付出的最小代价。那么就有两种转移:

① 如果 i i i 有多个子树,那么就把 s s s 分成多个子集,当做每个子树联通的点集,实际转移中分成两个 j , t j,t j,t 就可以实现
f i , s = f i , j + f i , t f_{i,s}=f_{i,j}+f_{i,t} fi,s=fi,j+fi,t

② 如果 i i i 只有一个相邻节点 j j j(且 i i i 还是根),那么
f i , s = f j , s + e i , j . w f_{i,s}=f_{j,s} + e_{i,j}.w fi,s=fj,s+ei,j.w
这个转移可以用 S P F A SPFA SPFA d i j dij dij 来实现

要注意的是:如果在实际情况下不是边权而是点权,那第一个转移方程还要再减去一个 a i , j a_{i,j} ai,j,因为重复加了一次。因为这个调了差不多一个晚上T_T


>代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#define N 5010
using namespace std;

queue<int> Q;
struct edge
{
	int to, nxt, w;
} e[N * 2];
int n, m, k, f[N][N], cnt, h[N], T, p[N];
bool vis[N];

void add (int u, int v, int w)
{
	e[++cnt] = (edge){v, h[u], w}; h[u] = cnt;
	e[++cnt] = (edge){u, h[v], w}; h[v] = cnt;
}
void spfa (int s)
{
	int u, v;
	while (!Q.empty())
	{
		u = Q.front(), Q.pop(), vis[u] = 0;
		for (int i = h[u]; i; i = e[i].nxt)
		{
			v = e[i].to;
			if (f[u][s] + e[i].w <= f[v][s])
			{
				f[v][s] = f[u][s] + e[i].w;
				if (!vis[v]) vis[v] = 1, Q.push (v);
			}
		}
	}
}

int main()
{
	scanf ("%d%d%d", &n, &m, &k);
	int u, v, w;
	for (int i = 1; i <= m; i++)
	{
		scanf ("%d%d%d", &u, &v, &w);
		add (u, v, w);
	}
	memset (f, 0x7f, sizeof (f));
	for (int i = 1; i <= k; i++)
	{
		scanf ("%d", &p[i]);
		f[p[i]][1 << (i - 1)] = 0;
	}
	for (int s = 1; s < (1 << k); s++)
	{
		memset (vis, 0, sizeof (vis));
		for (int i = 1; i <= n; i++)
		{
			for (int subs = s & (s - 1); subs; subs = s & (subs - 1))
			{
				f[i][s] = min (f[i][s], f[i][subs] + f[i][s ^ subs]);
			}
			if (f[i][s] != f[0][0])
			{
				Q.push (i);
				vis[i] = 1;
			}
		}
		spfa (s);
	}
	printf ("%d", f[p[1]][(1 << k) - 1]); //因为是树所以哪个点为根都无所谓
	return 0;
}

因篇幅问题不能全部显示,请点此查看更多更全内容

Top