• 最小斯坦纳树【模板】


    >Link

    luogu P6192


    >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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
  • 相关阅读:
    Tomcat启动闪退问题解决方法
    Flask 学习-26.JWT(JSON Web Token)生成Token
    第一章 网络基础知识
    python基础知识整理 08-多任务:进程
    ios 使用runtime实现自动解归档
    【趣学算法】第一章读书笔记
    一个十分好用且美观的vue3后台管理系统框架
    RabbitMQ(七)延迟队列
    性能测试准备方案
    使用HTML实现一个静态页面(含源码)
  • 原文地址:https://blog.csdn.net/qq_43010386/article/details/126288555