zoukankan      html  css  js  c++  java
  • 洛谷P3264 [JLOI2015]管道连接 (斯坦纳树)

    题目链接

    题目大意:有一张无向图,每条边有一定的花费,给出一些点集,让你从中选出一些边,用最小的花费将每个点集内的点相互连通,可以使用点集之外的点(如果需要的话)。

    算是斯坦纳树的入门题吧。

    什么是斯坦纳树呢?

    假定有这样的题目:给你一张无向图和一个点集,每条边有一定的花费,让你选出一些边使点集内的所有点连通,求最小花费。

    可以发现,如果点集大小为2,那么就转化成了一个两点间最短路问题。

    而如果点集大小为总点数,那么就转化成了一个最小生成树问题。

    进一步可以推出,如果点集大小为3,那么答案就相当于选出一个点,使该点到点集内三点的距离之和最短。

    那么如果点集大小为4,5,...,乃至更多,怎么办?

    随着点集的增加,情况也会越来越复杂,好像一时难以找到突破点。

    其实这类题有着通用的解法,也就是斯坦纳树的dp。

    设$dp[S][i]$为构造出以i为根(选根只是为了方便转移,无实际意义,相当于无根树转有根树),连通的点集为S的树的最小化费,则初始状态为$dp[1<<i][i]=0$,且存在两类转移:

     $dp[S][i]=left{egin{matrix}egin{aligned}&min{dp[S'][i]+dp[Soplus S'][i]}\&min{dp[S][j]+d[j][i]}end{aligned}end{matrix} ight.$

    其中$S'$是$S$的子集,$d[j][i]$表示点j与点i直接相连的边的花费。

    其中第一类转移是有序的,可以直接dp求解;而第二类是无序的,需要用SPFA求解(用Dijkstra理论上也可以,但通常SPFA更快)

    这样的转移还存在一个问题,就是有些边可能会重复经过,但总会存在一个不会重复的最优解,所以可以忽略掉这个问题。

    这是只有一个点集时的情况,如果有多个点集的话只需要再加一个转移就好了:

    $mi[S]=min{mi[S']+mi[Soplus S']}$

    其中$mi[S]$表示将点集S连通的最小花费,初始值为$mi[S]=min{dp[S][i]}$,且$S$和$Soplus S'$都要满足:对于任意一个点集中的点,要么不包含,要么全部包含。

    复杂度$O(n(3^k+Am2^k))$,A为SPFA的常数,一般较小。

     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 typedef long long ll;
     4 const int N=1000+10,inf=0x3f3f3f3f;
     5 int n,m,k,hd[N],ne,inq[N],dp[1<<10][N],mi[1<<10],SS[10+5],Y[N],X[N],ok[1<<10];
     6 struct E {int v,c,nxt;} e[N*6];
     7 void addedge(int u,int v,int c) {e[ne]= {v,c,hd[u]},hd[u]=ne++;}
     8 queue<int> q;
     9 void upd(int u,int c,int S) {
    10     if(dp[S][u]>c) {dp[S][u]=c; if(!inq[u])inq[u]=1,q.push(u);}
    11 }
    12 void spfa(int S) {
    13     while(q.size())q.pop();
    14     memset(inq,0,sizeof inq);
    15     for(int i=1; i<=n; ++i)if(dp[S][i]!=inf)q.push(i),inq[i]=1;
    16     while(q.size()) {
    17         int u=q.front();
    18         q.pop(),inq[u]=0;
    19         for(int i=hd[u]; ~i; i=e[i].nxt)upd(e[i].v,dp[S][u]+e[i].c,S);
    20     }
    21 }
    22 int main() {
    23     memset(hd,-1,sizeof hd),ne=0;
    24     scanf("%d%d%d",&n,&m,&k);
    25     while(m--) {
    26         int u,v,c;
    27         scanf("%d%d%d",&u,&v,&c);
    28         addedge(u,v,c);
    29         addedge(v,u,c);
    30     }
    31     for(int i=0; i<k; ++i) {
    32         int a,b;
    33         scanf("%d%d",&a,&b);
    34         Y[b]=i,X[i]=b;
    35         SS[a]|=1<<i;
    36     }
    37     for(int i=1; i<=10; ++i)if(SS[i])ok[SS[i]]=1;
    38     for(int i=1; i<=n; ++i)spfa(i);
    39     memset(dp,inf,sizeof dp);
    40     for(int i=0; i<k; ++i)dp[1<<i][X[i]]=0;
    41     for(int S=1; S<(1<<k); ++S) {
    42         for(int S2=(S-1)&S; S2; S2=(S2-1)&S)
    43             for(int i=1; i<=n; ++i)
    44                 dp[S][i]=min(dp[S][i],dp[S2][i]+dp[S^S2][i]);
    45         spfa(S);
    46         mi[S]=inf;
    47         for(int i=1; i<=n; ++i)mi[S]=min(mi[S],dp[S][i]);
    48     }
    49     for(int S=1; S<(1<<k); ++S)
    50         for(int S2=(S-1)&S; S2; S2=(S2-1)&S)if(ok[S2]&&ok[S^S2])
    51                 mi[S]=min(mi[S],mi[S2]+mi[S^S2]),ok[S]=1;
    52     printf("%d
    ",mi[(1<<k)-1]);
    53     return 0;
    54 }
  • 相关阅读:
    clr via c# 定制特性
    clr via c# delegate
    clr via c# Array2
    clr from c# 字符 ,字符串 和 文本处理
    clr via c# 接口
    clr via c# 泛型
    clr via c# 事件
    clr via c# 参数和属性
    程序设计入门——C语言 第6周编程练习 2 完数(5分)
    程序设计入门——C语言 第6周编程练习 1 分解质因数(5分)
  • 原文地址:https://www.cnblogs.com/asdfsag/p/11630257.html
Copyright © 2011-2022 走看看