斯坦纳树

本文详细介绍了斯坦纳树算法的基本概念及其应用场景,并通过一个具体的题目示例解释了如何使用动态规划的方法来解决斯坦纳树问题。文章还提供了一段伪代码帮助读者更好地理解算法实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

斯坦纳树


为什么我会写这个东西的博客呢?

某天我无意在洛谷里面找到了一道状压的题目,JLOI省选的然后当时兴冲冲开始做。想了3天3夜还是没有想出来,最后决定去搜题解……然后看到题解的时候——震惊!斯坦纳树是什么东西???于是去学习了斯坦纳树……


其实斯坦纳树很好理解,它像线段树一样用于解决一类问题。

想必一定听说过最小生成树,斯坦纳树和最小生成树很类似:在一个拥有N个点的图中,有M条边,每条边有各自的边权;指定某一些点,目的是建立最小的边使得这些边的边权和最小并且能够让指定点中的所有点连通。

注意,和最小生成树不同,斯坦纳树并不是要是图中所有点连通。

怎么办呢,如果单独的去spfa的话,我们只能够求出某一个点到另一个点的距离,但是我们并不能保证所有的点都被来连通了,怎么保证指定点的点集都连通呢?

我们发现最后的最优解一定是树,它一定是由若干个子树拼凑而成的,所以可以定义 dp[ S ][ i ] 表示以第 i 个点为根节点,并且指定点集 S0 (二进制状态)中有 S 状态的点连通。

注意:i 节点可以以任何点为根,S0 为指定节点的集合所有都被连通的状态(1111……11),而 S 是枚举的连通集合。

我们如何保证dp[ S ][ i ]中边的权值最小呢?

还是同样的原理:“最后的最优解一定是树,它一定是由若干个子树拼凑而成的”,那么当前状态 指定的点中连通子集 S 并且以 i 为根的最优边权和一定是由以 i 为根的两颗子树的合并,并且这两颗子树的所覆盖的指定点一定不会重合!(想一想,为什么)

因为这样的解一定不是最优的(你花了额外的边去连接重复的点)!


因此枚举子集 s,并且求出子集对于的 S 的补集 S^s ,min (dp [ s ][ i ] + dp [ S ^ s ] [ i ],dp [ S ] [ i ])更新 dp [ S ][ i ];

得到 <转移方程1>:

dp [ S ][ i ] = min(dp [ s ][ i ] + dp [ S ^ s ] [ i ],dp [ S ] [ i ]);


光这样不够,由于获得了当前以 i 为根的最优解,那么有可能会影响到与 i 相邻的所有点,甚至于所有点N对于只覆盖状态为S的点子集的最优解,也就是说:dp [ S ] [ i ] 比 dp [ S ] [ j ] + dis [ i ][ j ] 更优(j 属于 N,dis表示 i 和 j 的间接距离)的时候,我们可以用 dp [ S ] [ i ] 更新 dp [ S ] [ j ];

得到 <转移方程2>:

dp [ S ] [ v ] = min(dp [ S ] [ frt ] + dis [ i ],dp [ S ] [ v ]); (frt 为 spfa 的队首,v 与 frt 有边连接)

注意,对于单独的覆盖集合 S 的 i 只能更新覆盖集合为 S 的 j;不能更新其他集合的点;


代码:


#include <bits/stdc++.h>

std :: queue < int >  q ;

const  int  N = 10000 + 5 , N_ = 10 + 5 , SS = ( 1 << N_ ) , inf = 1e8 + 7 ;

int  head [ N ] , to [ N ] , dis [ N ] , nxt [ N ] , cn ;
int  dp [ N ] [ SS ] , n , m , p , S0 , cnt , x , y , w ;
bool  vis [ N ] ;

int  minx ( int  a , int  b ) {
	return  a < b ? a : b ;
}

void  create ( int  u , int  v , int  d ) {
	cn ++ ;
	dis [ cn ] = d ;
	to [ cn ] = v ;
	nxt [ cn ] = head [ u ] ;
	head [ u ] = cn ;  
}

void  spfa ( int  S ) {
	memset ( vis , false , sizeof ( vis ) ) ;
	while ( ! q . empty (  ) ) {
		int  frt = q . front (  ) ; q . pop (  ) ;
		vis [ frt ] = false ; 
		for ( int  i = head [ frt ] ; i ; i = nxt [ i ] ) {
			int  v = to [ i ] ;
			if ( dp [ S ] [ v ] > dp [ S ] [ frt ] + dis [ i ] ){
				dp [ S ] [ v ] = dp [ S ] [ frt ] + dis [ i ] ;
				if ( ! vis [ v ] ) {
					q . push ( v ) ;
					vis [ v ] = true ;
				}
			}
		}
	}
}

int  main (  ) {
	//斯坦纳树 Code ; 
	freopen ( "in.in" , "r" , stdin ) ;
	
	scanf  ( "%d%d%d" , & n , & m , & cnt ) ;
	//有 cnt 个点需要连通,并且这 cnt 个点的编号构成一个 1 ~ cnt 的序列 ; 
	S0 = ( 1 << cnt ) - 1 ;
	
	for ( int  i = 1 ; i <= m ; i ++ ) {
		scanf ( "%d%d%d" , & x , & y , & w ) ;
		create ( x , y , w ) ;
		create ( y , x , w ) ; 
	}
	
	for ( int  i = 1 ; i <= n ; i ++ ) 
	    for ( int  s = 0 ; s <= S0 ; s ++ ) 
	        dp [ s ] [ i ] = inf ; 
	
	for ( int  i = 1 ; i <= cnt ; i ++ )
		dp [ 1 << i - 1 ] [ i ] = 0 ;
	
	for ( int  S = 0 ; S <= S0 ; S ++ ) {
		
		for ( int  s = ( S - 1 ) & S ; s ; s = ( s - 1 ) & S ) //s 是 S 的子集 ;  
			for ( int  j = 1 ; j <= n ; j ++ )
			    if ( dp [ S ] [ j ] > dp [ s ] [ j ] + dp [ S ^ s ] [ j ] )
			        dp [ S ] [ j ] = dp [ s ] [ j ] + dp [ S ^ s ] [ j ] ;
		
		for ( int  j = 1 ; j <= n ; j ++ )
		    if ( dp [ S ] [ j ] < inf  &&  ! vis [ j ] )
		    	q . push ( j ) , vis [ j ] = true ;
		
		spfa ( S ) ;
		
	}
	
	int  ans = inf ; 
	
	for ( int  i = 1 ; i <= n ; i ++ )
		ans = minx ( ans , dp [ S0 ] [ i ] ) ;
	
	printf ( "%d" , ans ) ;
	
	return  0 ;
}


这份代码不适用于任何斯坦纳树的模板题目!!!

仅供理解!!!

Vivado2023是一款集成开发环境软件,用于设计和验证FPGA(现场可编程门阵列)和可编程逻辑器件。对于使用Vivado2023的用户来说,license是必不可少的。 Vivado2023的license是一种许可证,用于授权用户合法使用该软件。许可证分为多种类型,包括评估许可证、开发许可证和节点许可证等。每种许可证都有不同的使用条件和功能。 评估许可证是免费提供的,让用户可以在一段时间内试用Vivado2023的全部功能。用户可以使用这个许可证来了解软件的性能和特点,对于初学者和小规模项目来说是一个很好的选择。但是,使用评估许可证的用户在使用期限过后需要购买正式的许可证才能继续使用软件。 开发许可证是付费的,可以永久使用Vivado2023的全部功能。这种许可证适用于需要长期使用Vivado2023进行开发的用户,通常是专业的FPGA设计师或工程师。购买开发许可证可以享受Vivado2023的技术支持和更新服务,确保软件始终保持最新的版本和功能。 节点许可证是用于多设备或分布式设计的许可证,可以在多个计算机上安装Vivado2023,并共享使用。节点许可证适用于大规模项目或需要多个处理节点进行设计的用户,可以提高工作效率和资源利用率。 总之,Vivado2023 license是用户在使用Vivado2023时必须考虑的问题。用户可以根据自己的需求选择合适的许可证类型,以便获取最佳的软件使用体验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值