【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


题解

斯坦纳树裸题

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

斯坦纳树是NPC问题,不存在多项式时间内的解法,求解方法是状压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 <cstring>
#include <algorithm>
#define N 100010
using namespace std;
typedef long long ll;
typedef pair<ll , int> pr;
priority_queue<pr> q;
int head[N] , to[N << 2] , next[N << 2] , cnt , vis[33][N];
ll len[N << 2] , f[33][N];
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 , p , m , i , j , k , x , y;
    ll z , ans = 1ll << 62;
    scanf("%d%d%d" , &n , &p , &m);
    memset(f , 0x3f , sizeof(f));
    for(i = 0 ; i < p ; i ++ ) scanf("%d" , &x) , f[1 << i][x] = 0;
    for(i = 0 ; i < m ; i ++ ) scanf("%d%d%lld" , &x , &y , &z) , 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 ++ ) 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]));
        }
    }
    for(i = 1 ; i <= n ; i ++ ) ans = min(ans , f[(1 << p) - 1][i]);
    printf("%lld\n" , ans);
    return 0;
}

 

posted @ 2018-02-28 20:54  GXZlegend  阅读(902)  评论(0编辑  收藏  举报