About Steiner Tree

昨天刚学了一个叫Steiner Tree的东西 .

今天刚开博客就写出来吧 !


Steiner Tree 问题是对于给定的带权图G,选择其中的一些边W,使其中的一些点联通,并使sigma W最小 .

我们设 f[i]][j] 表示当前位置在 i , 已经经过的点集为 j 的最小花费 .

那么,有两种转移方式 :

f[i][j]=min(f[i][mask1]+f[i][mask2]),(j,mask1,mask2是包含i的集合,并且mask1+mask2=j,这里i重复)

f[i][j]=min(f[v][mask]+w[i][v]),(v是与i相邻的点)

对于第一种,我们枚举集合j,再枚举j的子集mask1,所以mask2=j-mask1

对于第二种,我们可以用SPFA来解决 .


那么,先枚举集合,在对每个集合跑一边当前集合的SPFA , 就可以求出答案了 .



hdu4085 : 答案可能由几个连通块组成,要用dp将答案整合 .

po个代码 :


#include <queue>
#include <cstdio>
using namespace std;
const int oo=(1<<30)-1;
int n,m,k;
int mkw[2050],dp[2050],f[2050][2050];
struct edge {
	int u,v,w;
	edge *nxt;
	edge (int u,int v,int w,edge *pre): u(u),v(v),w(w){nxt=pre;}
};
edge *he[2050];
queue <int> q;
bool inq[2050];
bool upd(int &n1,int n2) {
	if (n1>n2) return n1=n2,1; return 0;
}
void SPFA(int mask) {
	while (!q.empty()) {
		int u=q.front(); q.pop(); inq[u]=0;
		for (edge *i=he[u];i;i=i->nxt)  
			if (upd(f[i->v][mask|mkw[i->v]],f[u][mask]+i->w) && mask==mask|mkw[i->v] && !inq[i->v]) q.push(i->v),inq[i->v]=1;
	}
}
void Steiner() {
	int i,j,mask;
	for (i=1;i<1<<(2*k);i++) {
		for (j=1;j<=n;j++) {
			for (mask=(i-1)&i;mask;mask=(mask-1)&i) 
				upd(f[j][i|mkw[j]],f[j][mask|mkw[j]]+f[j][(i-mask)|mkw[j]]);
			if (f[j][i|mkw[j]]<oo && !inq[j]) 
				q.push(j),inq[j]=1; 
		}
		SPFA(i);
	}
}
bool check(int x){
    int r=0;
    for(int i=0;x;i++,x>>=1)
        r+=(x&1)*(i<k?1:-1);
    return r==0;
}
int main() {
	int T,i,j,a,b,c;
	scanf("%d",&T);
	n=m=0;
	while (T--) {
		for (i=1;i<=n;i++) mkw[i]=0;
		for (i=1;i<=n;i++) he[i]=NULL;
		scanf("%d%d%d",&n,&m,&k);
		for (i=1;i<=m;i++) {
			scanf("%d%d%d",&a,&b,&c);
			he[a]=new edge(a,b,c,he[a]);
			he[b]=new edge(b,a,c,he[b]);
		}
		for (i=1;i<=n;i++) 
			for (j=0;j<1<<(2*k);j++) f[i][j]=oo;
		for (i=1;i<=k;i++) {
			mkw[i]=1<<(i-1);
			mkw[n-k+i]=1<<(k+i-1);
			f[i][mkw[i]]=0;
			f[n-k+i][mkw[n-k+i]]=0;
		}
		Steiner();
		
		for(j=0;j<1<<(2*k);j++){
            dp[j]=oo;
            for(i=1;i<=n;i++) dp[j]=min(dp[j],f[i][j|mkw[i]]);
        }
        for(i=1;i<1<<(2*k);i++)
            if(check(i))
                for(j=i&(i-1);j;j=(j-1)&i)
                    if(check(j))
                        dp[i]=min(dp[i],dp[j]+dp[i-j]);
        if(dp[(1<<(2*k))-1]>=oo) printf("No solution\n");
        else printf("%d\n",dp[(1<<(2*k))-1]);
	}
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
以下是一个基于Prim算法的Steiner树求解代码实现,其中包括了注释和代码解释。注意,该代码实现是在完全图的基础上进行Steiner树求解的。 ```python import numpy as np import sys def steiner_tree_prim(n, m, edges, terminal_nodes): """ :param n: 图中总节点数 :param m: 图中总边数 :param edges: 边列表,每个元素为(u, v, w),表示u和v之间有一条权重为w的边 :param terminal_nodes: 终端节点列表,即需要包含在Steiner树中的节点列表 :return: 返回Steiner树的总权重和以及边列表 """ # 初始化邻接矩阵和终端节点集合 adj_matrix = np.zeros((n, n)) terminal_set = set(terminal_nodes) # 构建邻接矩阵,并且对终端节点集合进行去重 for edge in edges: u, v, w = edge adj_matrix[u][v] = w adj_matrix[v][u] = w terminal_set.add(u) terminal_set.add(v) # 重新计算节点数和终端节点数量 n = len(terminal_set) num_terminals = len(terminal_nodes) # 根据终端节点集合重新构造节点编号列表 idx_dict = {} node_idx = 0 for node in terminal_set: idx_dict[node] = node_idx node_idx += 1 # 初始化Steiner树的总权重和以及边列表 total_weight = 0 steiner_edges = [] # 初始化Prim算法需要使用的参数 visited = [False] * n dist = [sys.maxsize] * n parent = [-1] * n dist[0] = 0 # 从终端节点集合中任选一个节点开始遍历 current_node = idx_dict[terminal_nodes[0]] while not all(visited): # 将当前节点标记为已访问 visited[current_node] = True # 遍历当前节点的邻居 for neighbor_node in range(n): # 判断当前邻居是否已访问并且是否存在边相连 if not visited[neighbor_node] and adj_matrix[current_node][neighbor_node] > 0: # 计算新的距离 neighbor_dist = adj_matrix[current_node][neighbor_node] # 如果新的距离比之前的距离更短,则更新距离和父节点 if neighbor_dist < dist[neighbor_node]: dist[neighbor_node] = neighbor_dist parent[neighbor_node] = current_node # 找到下一个需要遍历的节点,即距离已知节点最近的未访问节点 min_dist = sys.maxsize for node in range(n): if not visited[node] and dist[node] < min_dist: min_dist = dist[node] current_node = node # 如果当前节点是终端节点,则将其与其父节点之间的边加入到Steiner树中 if idx_dict.get(current_node) in range(num_terminals): steiner_edges.append((idx_dict[current_node], parent[current_node], dist[current_node])) total_weight += dist[current_node] return total_weight, steiner_edges ``` 该代码实现依赖于Numpy和Sys库,因此在运行前需要先进行安装。在使用时,只需要传入总节点数、总边数、边列表和终端节点列表等参数即可求解Steiner树问题。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值