「长乐集训 2017 Day8」修路 (斯坦纳树)

题目描述

村子间的小路年久失修,为了保障村子之间的往来,AAA君决定带领大家修路。

村子可以看做是一个边带权的无向图GGG, GGG 由 nnn 个点与 mmm 条边组成,图中的点从 1∼n1 sim n1n 进行编号。现在请你选择图中的一些边,使得 ∀1≤i≤dforall 1 leq i leq d1id , iii 号点和 n−i+1n - i + 1ni+1号点可以通过你选择出的那些边连通,并且你要最小化选出的所有边的权值和。请你告诉AAA君这个最小权值和。

输入格式

第一行三个整数 nnn, mmm , ddd 表示图中的点数、边数与限制条件。

接下来 mmm 行每行三个整数 uiu_iui​​, viv_ivi​​ , wiw_iwi​​ 表示一条连接 (ui,vi)(u_i, v_i)(ui​​,vi​​) 的权值为叫的无向边。

输出格式

仅一行一个整数表示答案。若无解输出 −1-11 .

样例

样例输入

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

样例输出

9
  1 #include <bits/stdc++.h>
  2 
  3 using namespace std;
  4 const int maxn = 1e4+500;
  5 const int inf = 1<<29;
  6 int fa[maxn];
  7 int n,m,d,ST;
  8 int s[maxn];
  9 bool inq[maxn][1<<8];
 10 int f[maxn][1<<8];//f[i][j]根节点为i,连同状态为j的最小生成树
 11 int g[1<<8];// g 连同状态为j的最小生成树
 12 int ehead[maxn],ecnt;
 13 queue<pair<int,int> >que;
 14 inline void read(int&a){char c;while(!(((c=getchar())>='0')&&(c<='9')));a=c-'0';while(((c=getchar())>='0')&&(c<='9'))(a*=10)+=c-'0';}
 15 struct edge
 16 {
 17     int u,v,w,next;
 18 }edg[maxn*2];
 19 bool upd(int &u,int v)
 20 {
 21     return u>v?(u=v,1):0;
 22 }
 23 void add (int u,int v,int w)
 24 {
 25     edg[++ecnt] = (edge){u,v,w,ehead[u]};
 26     ehead[u]=ecnt;
 27     edg[++ecnt] = (edge){v,u,w,ehead[v]};
 28     ehead[v]=ecnt;
 29 
 30 }
 31 int findd (int x)
 32 {
 33     if (fa[x]==x) return x;
 34     else return fa[x]=findd(fa[x]);
 35 }
 36 void Union (int x,int y)
 37 {
 38     int fx = findd(x),fy = findd(y);
 39     if (fx==fy) return ;
 40     else {
 41         fa[fx] = fy;
 42         return ;
 43     }
 44 }
 45 void spfa ()
 46 {
 47     while (!que.empty()){
 48         pair<int,int> h = que.front();
 49         que.pop();
 50         int u = h.first,t = h.second;
 51         inq[u][t] = false;
 52         for (int j=ehead[u];j;j=edg[j].next){
 53             int v = edg[j].v,k = s[v]|t;
 54             if (upd(f[v][k],f[u][t]+edg[j].w)&&k==t){
 55                 if (!inq[v][k]){
 56                     que.push(make_pair(v,k));
 57                     inq[v][k] = true;
 58                 }
 59             }
 60         }
 61     }
 62 }
 63 bool check (int j)
 64 {
 65     for (int i=0;i<d;++i){
 66         int a = (j>>i)&1;
 67         int b = (j>>(i+d))&1;
 68         if (a!=b) return false;
 69     }
 70     return true;
 71 }
 72 void init ()
 73 {
 74     ecnt = 0;
 75     for (int i=0;i<maxn;++i)
 76         fa[i] = i;
 77     memset(inq,false,sizeof inq);
 78 }
 79 int main()
 80 {
 81     //freopen("road10.in","r",stdin);
 82     freopen("road.in","r",stdin);
 83     freopen("road.out","w",stdout);
 84     read(n);read(m);read(d);
 85     //scanf("%d%d%d",&n,&m,&d);
 86     init();
 87     ST=(1<<(2*d))-1;
 88     for (int i=1;i<=n;++i)
 89         for (int j=1;j<=ST;++j)
 90             f[i][j] = inf;
 91     for (int i=0;i<m;++i){
 92         int u,v,w;
 93         read(u);read(v);read(w);
 94         add(u,v,w);
 95         Union(u,v);
 96     }
 97     for (int i=1;i<=d;++i){
 98         s[i] = 1<<(i-1);
 99         f[i][s[i]] = 0;
100         s[n-i+1] = 1<<(i-1+d);
101         f[n-i+1][s[n-i+1]] = 0;
102         if (findd(i)!=findd(n-i+1)){
103             printf("-1
");
104             return 0;
105         }
106     }
107     for (int j=0;j<=ST;++j){
108         for (int i=1;i<=n;++i){
109             for (int k=j;k>0;k=((k-1)&j)){//枚举j的子集k
110                 upd(f[i][j],f[i][k|s[i]]+f[i][(j^k)|s[i]]);//当前根节点为i 更新
111             }
112         }
113         for (int i=1;i<=n;++i){
114             if (f[i][j]<inf){
115                 inq[i][j] = true;
116                 que.push(make_pair(i,j));
117             }
118         }
119         spfa();
120     }
121     for (int j=1;j<=ST;++j){
122         g[j] = inf;
123         if (!check(j)) continue ;
124         for (int i=1;i<=n;++i)
125             upd(g[j],f[i][j]);
126     }
127     for (int j=1;j<=ST;++j){
128         for (int k=j;k>0;k=(k-1)&j){
129             upd(g[j],g[k]+g[k^j]);
130         }
131     }
132     printf("%d
",g[ST]);
133     return 0;
134 }

转载:http://blog.csdn.net/gzh1992n/article/details/9119543

1. 什么是斯坦纳树?

       斯坦纳树问题是组合优化学科中的一个问题。将指定点集合中的所有点连通,且边权总和最小的生成树称为最小斯坦纳树(Minimal Steiner Tree),其实最小生成树是最小斯坦纳树的一种特殊情况。而斯坦纳树可以理解为使得指定集合中的点连通的树,但不一定最小。

2. 如何求解最小斯坦纳树?

      可以用DP求解,dp[i][state]表示以i为根,指定集合中的点的连通状态为state的生成树的最小总权值。

      转移方程有两重:

      第一重,先通过连通状态的子集进行转移。

      dp[i][state]=min{ dp[i][subset1]+dp[i][subset2] } 

      枚举子集的技巧可以用 for(sub=(state-1)&state;sub;sub=(sub-1)&state)。

      第二重,在当前枚举的连通状态下,对该连通状态进行松弛操作。

      dp[i][state]=min{ dp[i][state], dp[j][state]+e[i][j] }

      为什么只需对该连通状态进行松弛?因为更后面的连通状态会由先前的连通状态通过第一重转移得到,所以无需对别的连通状态松弛。松弛操作用SPFA即可。

      复杂度 O(n*3^k+cE*2^k)

      c为SPFA复杂度中的常数,E为边的数量,但几乎达不到全部边的数量,甚至非常小。3^k来自于子集的转移sum{C(i,n)*2^i} (1<=i<=n),用二项式展开求一下和。

 1 /*
 2  *  Steiner Tree:求,使得指定K个点连通的生成树的最小总权值
 3  *  st[i] 表示顶点i的标记值,如果i是指定集合内第m(0<=m<K)个点,则st[i]=1<<m 
 4  *  endSt=1<<K
 5  *  dptree[i][state] 表示以i为根,连通状态为state的生成树值
 6  */
 7 #define CLR(x,a) memset(x,a,sizeof(x))
 8 
 9 int dptree[N][1<<K],st[N],endSt;
10 bool vis[N][1<<K];
11 queue<int> que;
12 
13 int input()
14 {
15    /*
16     *    输入,并且返回指定集合元素个数K
17     *    因为有时候元素个数需要通过输入数据处理出来,所以单独开个输入函数。
18     */
19 }
20 
21 void initSteinerTree()
22 {
23     CLR(dptree,-1);
24     CLR(st,0);
25     for(int i=1;i<=n;i++) CLR(vis[i],0);
26     endSt=1<<input();
27     for(int i=1;i<=n;i++)
28         dptree[i][st[i]]=0;
29 }
30 
31 void update(int &a,int x)
32 {
33     a=(a>x || a==-1)? x : a;
34 }
35 
36 void SPFA(int state)
37 {
38     while(!que.empty()){
39         int u=que.front();
40         que.pop();
41         vis[u][state]=false;
42         for(int i=p[u];i!=-1;i=e[i].next){
43             int v=e[i].vid;
44             if(dptree[v][st[v]|state]==-1 || 
45                 dptree[v][st[v]|state]>dptree[u][state]+e[i].w){
46 
47                 dptree[v][st[v]|state]=dptree[u][state]+e[i].w;
48                 if(st[v]|state!=state || vis[v][state]) 
49                     continue; //只更新当前连通状态
50                 vis[v][state]=true;
51                 que.push(v);
52             }
53         }
54     }
55 }
56 
57 void steinerTree()
58 {
59     for(int j=1;j<endSt;j++){
60         for(int i=1;i<=n;i++){
61             if(st[i] && (st[i]&j)==0) continue;
62             for(int sub=(j-1)&j;sub;sub=(sub-1)&j){
63                 int x=st[i]|sub,y=st[i]|(j-sub);
64                 if(dptree[i][x]!=-1 && dptree[i][y]!=-1)
65                     update(dptree[i][j],dptree[i][x]+dptree[i][y]);
66             }
67             if(dptree[i][j]!=-1) 
68                 que.push(i),vis[i][j]=true;
69         }
70         SPFA(j);
71     }
72 }
 
 
原文地址:https://www.cnblogs.com/agenthtb/p/7689660.html