题目描述
村子间的小路年久失修,为了保障村子之间的往来,AAA君决定带领大家修路。
村子可以看做是一个边带权的无向图GGG, GGG 由 nnn 个点与 mmm 条边组成,图中的点从 1∼n1 sim n1∼n 进行编号。现在请你选择图中的一些边,使得 ∀1≤i≤dforall 1 leq i leq d∀1≤i≤d , iii 号点和 n−i+1n - i + 1n−i+1号点可以通过你选择出的那些边连通,并且你要最小化选出的所有边的权值和。请你告诉AAA君这个最小权值和。
输入格式
第一行三个整数 nnn, mmm , ddd 表示图中的点数、边数与限制条件。
接下来 mmm 行每行三个整数 uiu_iui, viv_ivi , wiw_iwi 表示一条连接 (ui,vi)(u_i, v_i)(ui,vi) 的权值为叫的无向边。
输出格式
仅一行一个整数表示答案。若无解输出 −1-1−1 .
样例
样例输入
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 }