笔记:最小斯坦纳树
最小斯坦纳树
定义
摘自百度百科的定义:
斯坦纳树问题是组合优化问题,与 最小生成树相似 ,是最短网络的一种。最小生成树是在给定的点集和边中寻求最短网络使所有点连通。而最小斯坦纳树允许在给定点外增加额外的点,使生成的最短网络开销最小。
实现
题目描述
给定一个包含
再给定包含
-
; -
为连通图; -
中所有边的权值和最小。
你只需要求出
数据范围
对于
保证给出的无向图连通,但 可能 存在重边和自环。
分析
要解决最小斯坦纳树,我们需要使用最短路算法 + 状压 DP。
首先,很显然的一点是:
证明:若
然后,我们发现
-
第一个式子可将
的两个互为补集的子集的状态合并。 -
第二个式子可将
的状态转移到另一个相邻节点。注意这个式子,像不像最短路?都是三角不等式,我们只需在状压时对每个状态跑一次最短路即可。
考虑 DP 的顺序:不难想到按
考虑答案的产生:
- 证明:因为
表示以 为根结点且包含 的树的最小边权和,而最小斯坦纳树显然包括 ,所以 中的任意一点为根都可以作为答案。
代码
#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]]
这两道题 基本相当于双倍经验 都有一个共同特点,就是钦定的关键点被分为了几个集合,可能形成斯坦纳树森林。(详情看题)
对于这种题,除了
然后,再通过状压枚举子集转移。
(说的不一定对,若有误,请指正。)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】