洛谷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 }
原文地址:https://www.cnblogs.com/asdfsag/p/11630257.html