【bzoj5180】[Baltic2016]Cities 斯坦纳树

题目描述

给定n个点,m条双向边的图。其中有k个点是重要的。每条边都有一定的长度。

现在要你选定一些边来构成一个图,要使得k个重要的点相互连通,求边的长度和的最小值。

输入

共m+2行

第1行:n,k,m,n个点,k个重要的点,m条边;

第2行共K个点

第3至第m+2行,每行包括3个数字,a,b,c,表示有一条从a到b长度为c的双向路径

k<=5

n<=10^5

1<=m<=2*(10^5)

输出

共1行,即最小长度和

样例输入

4 3 6
1 3 4
1 2 4
1 3 9
1 4 6
2 3 2
2 4 5
3 4 8

样例输出

11



题解

斯坦纳树裸题

斯坦纳树:给出一些点,选出若干条边使得这些点连通,求总边权的最值。

斯坦纳树是NP问题,不存在多项式时间内的解法,求解方法是状压dp。

设 $f[i][j]$ 表示选择若干条边,使得状态为 $i$ 的给定点连通,并且当前可以选择下一条边的端点为 $j$ 的最小边权和。初始状态 $f[2^i][pos[i]]=0$ ,其中 $pos[i]$ 为第 $i$ 个给定点的编号。

那么我们对于每个 $i$ 和 $j$ ,首先枚举 $i$ 的子集 $k$ ,用 $f[k][j]+f[i-k][j]$ 更新 $f[i][j]$ 。

然后再考虑同层转移:如果 $x$ 与 $y$ 边权为 $z$ ,用 $f[i][x]+z$ 更新 $f[i][y]$ ,用 $f[i][y]$ 更新 $f[i][x]$ 。容易发现这个转移就是最短路,因此使用堆优化Dijkstra跑一遍得出所有的 $f[i][j]$ 。

最终答案就是 $min\{f[2^k-1][i]\}$

时间复杂度 $O(3^k·n+2^k·m\log n)$

本来该是我的一血的 >_<

#include <queue>
#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
#define N 100010
using namespace std;
typedef long long ll;
queue<int> q;
int head[N] , to[N << 2] , next[N << 2] , cnt , inq[33][N];
ll len[N << 2] , f[33][N];
inline char nc()
{
	static char buf[100000] , *p1 , *p2;
	return p1 == p2 && (p2 = (p1 = buf) + fread(buf , 1 , 100000 , stdin) , p1 == p2) ? EOF : *p1 ++ ;
}
inline int read()
{
	int ret = 0; char ch = nc();
	while(!isdigit(ch)) ch = nc();
	while(isdigit(ch)) ret = ((ret + (ret << 2)) << 1) + (ch ^ ‘0‘) , ch = nc();
	return ret;
}
inline void add(int x , int y , ll z)
{
	to[++cnt] = y , len[cnt] = z , next[cnt] = head[x] , head[x] = cnt;
}
int main()
{
	int n = read() , p = read() , m = read() , i , j , k , x , y;
	ll z , ans = 1ll << 62;
	memset(f , 0x3f , sizeof(f));
	for(i = 0 ; i < p ; i ++ ) f[1 << i][read()] = 0;
	for(i = 0 ; i < m ; i ++ ) x = read() , y = read() , z = read() , add(x , y , z) , add(y , x , z);
	for(i = 1 ; i < (1 << p) ; i ++ )
	{
		for(j = i ; j ; j = i & (j - 1))
			for(k = 1 ; k <= n ; k ++ )
				f[i][k] = min(f[i][k] , f[j][k] + f[i ^ j][k]);
		for(j = 1 ; j <= n ; j ++ ) inq[i][j] = 1 , q.push(j);
		while(!q.empty())
		{
			x = q.front() , q.pop() , inq[i][x] = 0;
			for(j = head[x] ; j ; j = next[j])
			{
				if(f[i][to[j]] > f[i][x] + len[j])
				{
					f[i][to[j]] = f[i][x] + len[j];
					if(!inq[i][to[j]]) inq[i][to[j]] = 1 , q.push(to[j]);
				}
			}
		}
	}
	for(i = 1 ; i <= n ; i ++ ) ans = min(ans , f[(1 << p) - 1][i]);
	printf("%lld\n" , ans);
	return 0;
}

原文地址:https://www.cnblogs.com/GXZlegend/p/8485780.html

时间: 2024-10-14 12:19:31

【bzoj5180】[Baltic2016]Cities 斯坦纳树的相关文章

BZOJ_5180_[Baltic2016]Cities_ 斯坦纳树

题意: 给定n个点,m条双向边的图.其中有k个点是重要的.每条边都有一定的长度. 现在要你选定一些边来构成一个图,要使得k个重要的点相互连通,求边的长度和的最小值. 分析: 斯坦纳树裸题 dis[i][j]表示关键点连通状态为i,当前在点j的最小花费 有两个转移:内部枚举子集,外部spfa转移 这道题卡spfa,那我们用dij就好啦 代码: #include <stdio.h> #include <string.h> #include <algorithm> #incl

FJoi2017 1月20日模拟赛 直线斯坦纳树(暴力+最小生成树+骗分+人工构造+随机乱搞)

[题目描述] 给定二维平面上n个整点,求该图的一个直线斯坦纳树,使得树的边长度总和尽量小. 直线斯坦纳树:使所有给定的点连通的树,所有边必须平行于坐标轴,允许在给定点外增加额外的中间节点. 如下图所示为两种直线斯坦纳树的生成方案,蓝色点为给定的点,红色点为中间节点. [输入格式] 第一行一个整数n,表示点数. 以下n行,每行两个整数表示点的x,y坐标. [输出格式] 第一行一个整数m,表示中间节点的个数. 必须满足m <= 10 * n 以下m行,每行2个整数表示中间节点的坐标. 以下n+m-1

BZOJ 3205 [Apio2013]机器人 ——斯坦纳树

腊鸡题目,实在卡不过去. (改了一下午) 就是裸的斯坦纳树的题目,一方面合并子集,另一方面SPFA迭代求解. 优化了许多地方,甚至基数排序都写了. 还是T到死,不打算改了,就这样吧 #include <map> #include <cmath> #include <queue> #include <cstdio> #include <cstring> #include <iostream> #include <algorithm

HDU 4085 斯坦纳树模板题

Dig The Wells Time Limit: 6000/2000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Submission(s): 971    Accepted Submission(s): 416 Problem Description You may all know the famous story "Three monks". Recently they find som

【Foreign】修路 [斯坦纳树]

修路 Time Limit: 20 Sec  Memory Limit: 256 MB Description Input Output 仅一行一个整数表示答案. Sample Input 5 5 2 1 3 4 3 5 2 2 3 1 3 4 4 2 4 3 Sample Output 9 HINT Main idea 给定若干对点,选择若干边,询问满足每对点都连通的最小代价. Source 发现 d 非常小,所以我们显然可以使用斯坦纳树来求解. 斯坦纳树是用来解决这种问题的:给定若干关键点,

BZOJ 2595 [Wc2008]游览计划 ——斯坦纳树

[题目分析] 斯坦纳树=子集DP+SPFA? 用来学习斯坦纳树的模板. 大概就是用二进制来表示树包含的点,然后用跟几点表示树的形态. 更新分为两种,一种是合并两个子集,一种是换根,换根用SPFA迭代即可. [代码] #include <cstdio> #include <cstring> #include <cstdlib> #include <cmath> #include <set> #include <map> #include

HDU 4085 斯坦纳树

题目大意: 给定无向图,让前k个点都能到达后k个点(保护地)中的一个,而且前k个点每个需要占据后k个中的一个,相互不冲突 找到实现这个条件达到的选择边的最小总权值 这里很容易看出,最后选到的边不保证整个图是联通的 我们只要计算出每一个连通的最小情况,最后跑一遍dfs就能计算出答案了 那么用dp[i][j]表示 i 点为根得到联通状态为 j 的情况需要选到的边的最小总权值 这个用斯坦纳树的思想就可以做到的 对于每一个状态,都用spfa跑一遍得到最优解 dp[i][j] = min(dp[i][j]

【BZOJ2595】【Wc2008】游览计划、斯坦纳树

题解:斯坦纳树,实现神马的在代码里面有还看得过去的注释. 代码: #include <queue> #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> #define N 15 #define inf 0x3f3f3f3f using namespace std; const int dx[]={0,0,1,-1}; const int dy[

斯坦纳树模板

屌炸天阿什么东西都有 丢 //斯坦纳树模板 让k个点联通的最小生成树 复杂度 n*3^k #include<iostream> #include<cstring> #include<set> #include<map> #include<cmath> #include<stack> #include<queue> #include<deque> #include<list> #include<