【bzoj4006】[JLOI2015]管道连接 斯坦纳树+状压dp

题目描述

给出一张 $n$ 个点 $m$ 条边的无向图和 $p$ 个特殊点,每个特殊点有一个颜色。要求选出若干条边,使得颜色相同的特殊点在同一个连通块内。输出最小边权和。

输入

第一行包含三个整数 n;m;p,表示情报站的数量,可以建立的通道数量和重要情报站的数

量。接下来 m 行,每行包含三个整数 ui;vi;wi,表示可以建立的通道。最后有 p 行,每行包含
两个整数 ci;di,表示重要情报站的频道和情报站的编号。

输出

输出一行一个整数,表示任意相同频道的情报站之间都建立通道连接所花费的最少资源总量。

样例输入

5 8 4
1 2 3
1 3 2
1 5 1
2 4 2
2 5 1
3 4 3
3 5 1
4 5 1
1 1
1 2
2 3
2 4

样例输出

4


题解

斯坦纳树+状压dp

由于点数很小,因此求出斯坦纳树,求法可以参考 【bzoj2595】[Wc2008]游览计划

那么对于本题,由于不要求选出的只有一个连通块,因此不能直接拿到答案。

考虑状压dp,设 $g[i]$ 表示状态为 $i$ 的所有颜色满足同颜色连通的最小代价。那么如果只有一个连通块则答案就是斯坦纳树的 $f[v][i]$ ,其中 $v$ 是所有颜色状态为 $i$ 的点对应的状态。不只有一个连通块的话则枚举子集转移。

时间复杂度 $O(3^p·n+2^p·m\log n)=O(能过)$ 

#include <queue>
#include <cstdio>
#include <cstring>
#include <utility>
using namespace std;
typedef pair<int , int> pr;
priority_queue<pr> q;
int head[1010] , to[6010] , len[6010] , next[6010] , cnt , c[15] , d[15] , f[1030][1010] , vis[1030][1010] , g[1030];
inline void add(int x , int y , int z)
{
	to[++cnt] = y , len[cnt] = z , next[cnt] = head[x] , head[x] = cnt;
}
int main()
{
	int n , m , p , i , j , k , x , y , z;
	scanf("%d%d%d" , &n , &m , &p);
	memset(f , 0x3f , sizeof(f));
	for(i = 1 ; i <= m ; i ++ ) scanf("%d%d%d" , &x , &y , &z) , add(x , y , z) , add(y , x , z);
	for(i = 1 ; i <= p ; i ++ ) scanf("%d%d" , &c[i] , &d[i]) , f[1 << (i - 1)][d[i]] = 0;
	for(i = 1 ; i <= n ; i ++ ) f[0][i] = 0;
	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 ++ ) q.push(pr(-f[i][j] , j));
		while(!q.empty())
		{
			x = q.top().second , q.pop();
			if(vis[i][x]) continue;
			vis[i][x] = 1;
			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] , q.push(pr(-f[i][to[j]] , to[j]));
		}
	}
	memset(g , 0x3f , sizeof(g));
	for(i = 1 ; i < 1 << p ; i ++ )
	{
		k = 0;
		for(j = 1 ; j <= p ; j ++ )
			if(i & (1 << (c[j] - 1)))
				k |= (1 << (j - 1));
		for(j = 1 ; j <= n ; j ++ ) g[i] = min(g[i] , f[k][j]);
		for(j = i ; j ; j = i & (j - 1)) g[i] = min(g[i] , g[j] + g[i ^ j]);
	}
	printf("%d\n" , g[(1 << p) - 1]);
	return 0;
}

 

 

posted @ 2017-12-20 20:57  GXZlegend  阅读(484)  评论(0编辑  收藏  举报