题目链接
题目大意:有一张无向图,每条边有一定的花费,给出一些点集,让你从中选出一些边,用最小的花费将每个点集内的点相互连通,可以使用点集之外的点(如果需要的话)。
算是斯坦纳树的入门题吧。
什么是斯坦纳树呢?
假定有这样的题目:给你一张无向图和一个点集,每条边有一定的花费,让你选出一些边使点集内的所有点连通,求最小花费。
可以发现,如果点集大小为2,那么就转化成了一个两点间最短路问题。
而如果点集大小为总点数,那么就转化成了一个最小生成树问题。
进一步可以推出,如果点集大小为3,那么答案就相当于选出一个点,使该点到点集内三点的距离之和最短。
那么如果点集大小为4,5,...,乃至更多,怎么办?
随着点集的增加,情况也会越来越复杂,好像一时难以找到突破点。
其实这类题有着通用的解法,也就是斯坦纳树的dp。
设$dp[S][i]$为构造出以i为根(选根只是为了方便转移,无实际意义,相当于无根树转有根树),连通的点集为S的树的最小化费,则初始状态为$dp[1<
$dp[S][i]=\left\{\begin{matrix}\begin{aligned}&min\{dp[S'][i]+dp[S\oplus S'][i]\}\\&min\{dp[S][j]+d[j][i]\}\end{aligned}\end{matrix}\right.$
其中$S'$是$S$的子集,$d[j][i]$表示点j与点i直接相连的边的花费。
其中第一类转移是有序的,可以直接dp求解;而第二类是无序的,需要用SPFA求解(用Dijkstra理论上也可以,但通常SPFA更快)
这样的转移还存在一个问题,就是有些边可能会重复经过,但总会存在一个不会重复的最优解,所以可以忽略掉这个问题。
这是只有一个点集时的情况,如果有多个点集的话只需要再加一个转移就好了:
$mi[S]=min\{mi[S']+mi[S\oplus S']\}$
其中$mi[S]$表示将点集S连通的最小花费,初始值为$mi[S]=min\{dp[S][i]\}$,且$S$和$S\oplus S'$都要满足:对于任意一个点集中的点,要么不包含,要么全部包含。
复杂度$O(n(3^k+Am2^k))$,A为SPFA的常数,一般较小。
1 #include2 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 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 1<0; 41 for(int S=1; S<(1< 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< 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\n",mi[(1< 1]); 53 return 0; 54 }