首页 > 其他分享 >笔记:最小斯坦纳树

笔记:最小斯坦纳树

时间:2024-11-23 10:35:31浏览次数:11  
标签:leq int 边权 最小 笔记 斯坦纳 为根

最小斯坦纳树

定义

摘自百度百科的定义:

斯坦纳树问题是组合优化问题,与 最小生成树相似 ,是最短网络的一种。最小生成树是在给定的点集和边中寻求最短网络使所有点连通。而最小斯坦纳树允许在给定点外增加额外的点,使生成的最短网络开销最小。

实现

例题:P6192 【模板】最小斯坦纳树 - 洛谷

题目描述

给定一个包含 \(n\) 个结点和 \(m\) 条带权边的无向连通图 \(G=(V,E)\)。

再给定包含 \(k\) 个结点的点集 \(S\),选出 \(G\) 的子图 \(G'=(V',E')\),使得:

  1. \(S\subseteq V'\);

  2. \(G'\) 为连通图;

  3. \(E'\) 中所有边的权值和最小。

你只需要求出 \(E'\) 中所有边的权值和。

数据范围

对于 \(100\%\) 的数据,\(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\)。

保证给出的无向图连通,但 可能 存在重边和自环。

分析

要解决最小斯坦纳树,我们需要使用最短路算法 + 状压 DP。

首先,很显然的一点是:\(G'\) 是一棵树。

证明:若 \(G′\) 中包含环,那么将环中边权最大的边删去后,\(G′\) 仍连通且边权和变得更小,与前面假设的边权和最小矛盾,故 \(G′\) 不含环。

然后,我们发现 \(k\) 很小,可以往状压方面去想。设 \(f[i][S]\) 表示以 \(i\) 为根的树,选择的点集为 \(S\)(题目里面说的 \(k\) 个点)时的最小边权和,则可得到方程:

\[\begin{aligned} f[i][S] &= f[i][S - T] + f[i][T]\:\:(T \subseteq S)\\ f[i][S] &= f[j][S] + w(i, j) \end{aligned} \]

  • 第一个式子可将 \(S\) 的两个互为补集的子集的状态合并。

  • 第二个式子可将 \(i\) 的状态转移到另一个相邻节点。注意这个式子,像不像最短路?都是三角不等式,我们只需在状压时对每个状态跑一次最短路即可。

考虑 DP 的顺序:不难想到按 \(S\) 从小到大枚举即可。

考虑答案的产生:\(\forall i \in S\),\(f[i][S]\) 均为答案。

  • 证明:因为 \(f[i][S]\) 表示以 \(i\) 为根结点且包含 \(S\) 的树的最小边权和,而最小斯坦纳树显然包括 \(S\),所以 \(S\) 中的任意一点为根都可以作为答案。

代码

#include <bits/stdc++.h>
using namespace std;

const int maxn = 105, maxm = 505, maxk = 10;
int n, m, k;
int head[maxn];
struct Edge {
    int to, weight, next;
} edge[maxm << 1];
struct Node {
    int id, dis;
    bool operator < (const Node &rhs) const {
        return dis > rhs.dis;
    }
};
int f[maxn][1 << maxk];

inline void insertEdge(int u, int v, int w) {
    static int edgecnt;
    edge[++edgecnt].to = v;
    edge[edgecnt].weight = w;
    edge[edgecnt].next = head[u];
    head[u] = edgecnt;
}

inline void dijkstra(int S) {
    priority_queue<Node> que;
    for (int i = 1; i <= n; i++) {
        if (f[i][S] == 0x3f3f3f3f) continue;
        que.push(Node{ i, f[i][S] }); // 只有已经松弛过的点才加入优先队列
    }
    while (!que.empty()) {
        int u = que.top().id;
        que.pop();
        for (int i = head[u]; i; i = edge[i].next) {
            int v = edge[i].to, w = edge[i].weight;
            if (f[v][S] > f[u][S] + w) {
                f[v][S] = f[u][S] + w; // 和最短路是一样的
                que.push(Node{ v, f[v][S] });
            }
        }
    }
}

int main() {
    scanf("%d%d%d", &n, &m, &k);
    for (int i = 1; i <= m; i++) {
        int u, v, w;
        scanf("%d%d%d", &u, &v, &w);
        insertEdge(u, v, w);
        insertEdge(v, u, w);
    }
    memset(f, 0x3f, sizeof(f)); // 记得初始化为正无穷
    int vertex;
    for (int i = 1; i <= k; i++) {
        scanf("%d", &vertex);
        f[vertex][1 << (i - 1)] = 0; // 当集合中只包含一个点的时候,边权和显然为 0
    }
    for (int i = 1; i <= n; i++) {
        f[i][0] = 0; // 一个点都没有显然也是 0
    }
    for (int S = 1; S < (1 << k); S++) {
        for (int i = 1; i <= n; i++) { // 以每个点为根都跑一遍
            for (int j = S & (S - 1); j; j = S & (j - 1)) { // 枚举当前 S 的子集(不含空集和 S 本身,原因自己想)
                f[i][S] = min(f[i][S], f[i][S ^ j] + f[i][j]);
            }
        }
        dijkstra(S); // 最短路
    }
    printf("%d", f[vertex][(1 << k) - 1]); // S 中的每个点都可作为答案,所以就干脆直接用最后读入的 vertex
    return 0;
}

最小斯坦纳树森林

[[P3264 JLOI2015 管道连接]]
[[Peach Blossom Spring]]
这两道题 基本相当于双倍经验 都有一个共同特点,就是钦定的关键点被分为了几个集合,可能形成斯坦纳树森林。(详情看题)

对于这种题,除了 \(f[i][S]\) 表示以 \(i\) 为根,关键点点集为 \(S\) 时的最小边权和,还有一个 \(g[S]\),表示所选关键点点集为 \(S\) 的时候所构成的斯坦纳树森林的最小边权和,并且这个 \(g[S]\) 在不同的题目中会有不同的前置条件,只有所有前置条件满足的时候,才可以通过 \(f\) 转移。

\[g[S] = min(f[i][S]) \]

然后,再通过状压枚举子集转移。

\[g[S] = \style\nolimit\min_{T \subseteq S}(g[S], g[S - T] + g[T]) \]

(说的不一定对,若有误,请指正。)

标签:leq,int,边权,最小,笔记,斯坦纳,为根
From: https://www.cnblogs.com/jxyanglinus/p/18564195

相关文章

  • 笔记:二维哈希
    二维哈希前置芝士哈希前缀和教程二维哈希板子P10474BeiJing2011Matrix矩阵哈希-洛谷代码#include<bits/stdc++.h>usingnamespacestd;typedefunsignedlonglongull;constullRowBase=131,ColBase=1313;constintmaxn=1005;intn,m,a,b;ul......
  • 笔记:二分图
    概念二分图:又称作二部图,设\(G=(V,E)\)是一个无向图,如果顶点集\(V\)可分割为两个互不相交的子集\(A,B\),并且图中的每条边\((u,v)\)所关联的两个顶点\(u,v\)分别属于这两个顶点集\((u\inA,v\inB)\),则称图\(G\)为一个二分图。也就是说一个图被划分成了两个......
  • Servlet -个人理解笔记
    Servlet的作用        Servlet主要是为了衔接web应用的前端和后端的,作为它们俩中间数据交换的桥梁,现在很多web项目都是前后端分离的,前端写前端的后端写后端的,但是他俩所用的编程语言是有区别的,怎么实现它们之间的数据交换呢?Servlet就是为了解决这个,它是用java编写的,目......
  • JavaWeb知识点总结 我的学习笔记
    JavaWeb我的学习笔记一、动态网页开发1.动态网页2.系统架构C/S架构B/S架构B/S与C/S的比较3.URL通信三要素4.Tomcat服务器二、Servlet1.Servlet简介2.Servlet快速入门入门样例执行原理3.Servlet的体系结构4.servlet的十大方法5.Servlet生命周期6.在web.xml中配置servl......
  • 一个可以调节笔记本亮度的程序
    在我这台笔记本上,当我把显示模式调为读显时发现右下角的亮度不能调了,就像这样 听说时nvidia显卡不适配的问题咱也不知道呀于是我就用java写了程序来调节,用了俩个多月,没啥问题的打开就是这样拉动直接就可以调节源码importjavax.swing.*;importjavax.swing.event.Cha......
  • Linux笔记---Makefile的简单用法
    1.什么是MakefileMakefile是一种用于自动化构建和管理项目的工具,特别是在软件开发中非常常见。它包含了一系列规则(rules)和指令,描述了如何编译和链接源代码文件,以及生成最终的可执行文件或库文件。简单来说,在系统中存在一个叫做make的命令,该命令被使用之后,会在当前目录下......
  • Spring学习笔记_10-@Component
    @Component1.介绍在项目开发过程中,我们自己编写的类如果想注入到Spring中,由Spring来管理Bean的生命周期,就可以使用@Component注解将其注入到IOC容器中。@Component注解还有三个衍生注解,那就是@Repository、@Service和@Controller注解,并且衍生出的注解通常会在使用MVC架构开......
  • Spring学习笔记_09——Environment
    Environment1.介绍Spring框架中的Environment是一个非常重要的概念,它提供了访问当前运行环境配置的API。Environment是一个接口,它包含了多个方法,用于获取配置参数、设置默认配置源、激活特定的配置文件等。在Spring应用中,Environment实例通常被注入到需要访问配置信息的......
  • 职业技能大赛—物联网应用开发赛项(Ubuntun_Linux)精华笔记 (03)
    MySQL中的show各种查看命令介绍//全局变量在MySQL启动的时候由服务器自动将它们初始化为默认值,这些默认值可以通过更改my.ini这个文件来更改。//MySQL中的show各种查看命令介绍是必须了解的Mysql基础操作还请您认真看下去 1.使用show查看showtables或showtablesfrom......
  • Oracle+11g+笔记(8)-备份与恢复机制
    Oracle+11g+笔记(8)-备份与恢复机制8、备份与恢复机制8.1备份与恢复的方法数据库的备份是对数据库信息的一种操作系统备份。这些信息可能是数据库的物理结构文件,也可能是某一部分数据。在数据库正常运行时,就应该考虑到数据库可能出现故障,而对数据库实施有效的备份,保证......