【算法竞赛学习笔记】超好懂的斯坦纳树详解!!!

19 篇文章 0 订阅

title : 斯坦纳树
tags : ACM 图论
date : 2021-6-26
author : Linno


什么是斯坦纳树

给定 n 个点 A1,A2,⋯,An试求连接此n个点,总长最短的直线段连接系统,并且任意两点都可由系统中的直线段组成的折线连接起来。他们将此新问题称为 斯坦纳树问题
斯坦纳树问题是组合优化问题,与最小生成树相似,是最短网络的一种。最小生成树是在给定的点集和边中寻求最短网络使所有点连通。而最小斯坦纳树允许在给定点外增加额外的点,使生成的最短网络开销最小。

将指定点集合中的所有点连通,且边权总和最小的生成树称为最小斯坦纳树(Minimal Steiner Tree)

算法推导

这是一个组合优化问题,可以用状压DP来解决。

首先有已经结论:答案的子图一定是树。

我们首先钦定一个树根,设dp(i,S)表示以i为根,包含S点集的最小代价。

考虑状态转移:
若 i 的 度 数 等 于 1 , 则 d p ( j , s ) + w ( j , i ) − > d p ( i , s ) 若 i 的 度 数 大 于 1 , 则 d p ( i , T ) + d p ( i , S − T ) − > d p ( i , S ) ( T ⊆ S ) 若i的度数等于1,则dp(j,s)+w(j,i)->dp(i,s)\\ 若i的度数大于1,则dp(i,T)+dp(i,S-T)->dp(i,S)(T\subseteq S) i1dp(j,s)+w(j,i)>dp(i,s)i1dp(i,T)+dp(i,ST)>dp(i,S)(TS)
状态转移时对每个S,将图做松弛操作,采用dijkstra实现。

总的时间复杂度 O ( n × 3 k + m l o g m × 2 k ) O(n×3^k+mlog m ×2^k) O(n×3k+mlogm×2k)

在这里插入图片描述

(洛谷P6192【模板】最小斯坦纳树 图示)
代码
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f3f3f3f
using namespace std;
const int maxn=510;
const int INF=0x3f3f3f3f;
typedef long long ll;
typedef pair<int,int> P;
int n,m,k,u,v,w;
struct E{
	int to,next,dis;	
}edge[maxn<<1];

int head[maxn<<1],tree[maxn<<1],cnt;
int dp[maxn][5005],vis[maxn];
//dp[i][j]表示以i为根的一棵树,包含集合j中所有点的最小边权值和 
int key[maxn];  //关键点 
priority_queue<P,vector<P>,greater<P> >q;

void addedge(int from,int to,int dis){
	edge[++cnt].next=head[from];
	edge[cnt].to=to;
	edge[cnt].dis=dis;
	head[from]=cnt;
	tree[cnt]=to; 
}

void dijkstra(int s){  //迪杰斯特拉堆优化算法 
	memset(vis,0,sizeof(vis));
	while(!q.empty()){
		P fro=q.top();
		q.pop();	
		if(vis[fro.second]) continue;
		vis[fro.second]=1;
		for(int i=head[fro.second];i;i=edge[i].next){
			if(dp[tree[i]][s]>dp[fro.second][s]+edge[i].dis){
				dp[tree[i]][s]=dp[fro.second][s]+edge[i].dis;
				//再当前子集连通状态下进行边的松弛操作 
				q.push(P(dp[tree[i]][s],tree[i]));
			}
		}
	}
} 

signed main(){
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	memset(dp,INF,sizeof(dp));
	cin>>n>>m>>k;
	for(int i=1;i<=m;i++){ //建无向图 
		cin>>u>>v>>w;
		addedge(u,v,w);
		addedge(v,u,w);
	}
	for(int i=1;i<=k;i++){
		cin>>key[i]; 
		dp[key[i]][1<<(i-1)]=0;
	}
	for(int s=1;s<(1<<k);s++){ //表示点集
		for(int i=1;i<=n;i++){  //中间部分
			for(int subs=s&(s-1);subs;subs=s&(subs-1)) //子图 
				dp[i][s]=min(dp[i][s],dp[i][subs]+dp[i][s^subs]);
			if(dp[i][s]!=INF) q.push(P(dp[i][s],i));	
		}
		dijkstra(s);
	}
	cout<<dp[key[1]][(1<<k)-1]<<endl;
	return 0;
}

参考资料

https://www.luogu.com.cn/problem/solution/P6192

  • 2
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
斯坦纳树问题是一个NP难问题,没有多项式时间算法可以完全解决。因此,我们通常使用近似算法来解决这个问题。 下面是一个简单的近似算法,以C语言实现。 首先,我们需要用动态规划算法计算出所有的子集的最小生成树,然后对于每个子集,选择它的最小生成树,并将它们合并成一个整体的最小生成树。 算法的核心代码如下: ```c #include <stdio.h> #include <limits.h> #define MAXN 100 #define MAXM 10000 typedef struct { int u, v, w; } Edge; int n, m; Edge edges[MAXM]; int dp[1 << MAXN][MAXN]; int parent[1 << MAXN][MAXN]; int min(int a, int b) { return a < b ? a : b; } int readGraph() { if (scanf("%d%d", &n, &m) != 2) { return 0; } for (int i = 0; i < m; ++i) { scanf("%d%d%d", &edges[i].u, &edges[i].v, &edges[i].w); } return 1; } int cmp(const void *a, const void *b) { return ((Edge *) a)->w - ((Edge *) b)->w; } void sortEdges() { qsort(edges, m, sizeof(Edge), cmp); } void initialize() { for (int i = 0; i < (1 << n); ++i) { for (int j = 0; j < n; ++j) { dp[i][j] = INT_MAX; } } for (int i = 0; i < n; ++i) { dp[1 << i][i] = 0; } } void computeDP() { for (int s = 0; s < (1 << n); ++s) { for (int i = 0; i < n; ++i) { if ((s & (1 << i)) == 0) { continue; } for (int j = 0; j < m; ++j) { int u = edges[j].u; int v = edges[j].v; if ((s & (1 << u)) && (s & (1 << v))) { int t = s ^ (1 << i); int w = dp[t][u] + dp[t][v] + edges[j].w; if (w < dp[s][i]) { dp[s][i] = w; parent[s][i] = j; } } } } } } void printSteinerTree() { int s = (1 << n) - 1; int root = 0; for (int i = 1; i < n; ++i) { if (dp[s][i] < dp[s][root]) { root = i; } } printf("Minimum Steiner Tree Cost = %d\n", dp[s][root]); while (s) { int j = parent[s][root]; int t = s ^ (1 << root); if (dp[s][root] == dp[t][edges[j].u] + dp[t][edges[j].v] + edges[j].w) { s = t; printf("(%d,%d) ", edges[j].u, edges[j].v); root = edges[j].u; } else { j = edges[j].v; s &= ~(1 << root); root = j; } } printf("\n"); } int main() { if (!readGraph()) { return 1; } sortEdges(); initialize(); computeDP(); printSteinerTree(); return 0; } ``` 该算法的时间复杂度为$O(3^n * n^2 + m * 2^n)$,其中$m$是边的数量。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值