最小斯坦纳树
问题描述:
给定一个包含 \(n\) 个结点和 \(m\) 条带权边的无向连通图 \(G=(V,E)\)。
再给定包含 \(k\) 个结点的点集 \(S\),选出 \(G\) 的子图 \(G'=(V',E')\) 使得:
-
\(S\subseteq V'\)
-
\(G′\) 为连通图;
-
\(E′\) 中所有边的权值和最小。
你只需要求出 \(E′\)中所有边的权值和。
分析:
\(dp[i][s]\) 表示 以 i 为根的一棵树,包含集合 S 中所有点的最小代价 \(i\) 号点不一定在 \(s\) 中
-
\(w[i][j] + dp[j][s] -> dp[i][s]\)
-
\(dp[i][T] + dp[i][S-T] -> dp[i][S]\quad(T \subseteq S)\)
第二类转移可以用枚举子集来转移,第一类转移是一个三角不等式,可以用\(spfa\)或者\(dijkstra\)
第一类转移复杂度为\(O(m\log m \times 2^k)\)
第二类转移复杂度为\(O(n\times 3^n)\) ,子集枚举的时间复杂度可以用二项式定理求出
代码:
const int N = 100 + 5; const int M = 1010; int n, m, k, x, y, z, tot; int head[N], ver[M], nxt[M], edge[M], dp[N][4200]; int p[N], vis[N]; priority_queue<pair<int,int>> q; void add(int x, int y, int z){ ver[++tot] = y; edge[tot] = z, nxt[tot] = head[x], head[x] = tot; } void dijkstra(int s){ memset(vis, 0, sizeof vis); while(q.size()){ auto t = q.top();q.pop(); int x = t.second; if(vis[x]) continue; vis[x] = 1; for(int i=head[x];i;i=nxt[i]){ int y = ver[i]; if(dp[y][s] > dp[x][s] + edge[i]){ dp[y][s] = dp[x][s] + edge[i]; q.push(make_pair(-dp[y][s], y)); } } } } int main(){ scanf("%d%d%d", &n, &m, &k); for(int i=1;i<=m;i++){ scanf("%d%d%d", &x, &y, &z); add(x, y, z);add(y, x, z); } memset(dp, 0x3f, sizeof dp); for(int i=1;i<=k;i++){ scanf("%d", &p[i]); dp[p[i]][1<<(i-1)] = 0; } for(int s = 1; s < (1 << k); s++){ for(int i=1; i <= n; i++){ for(int subs = (s-1)&s; subs; subs = s & (subs-1)){ dp[i][s] = min(dp[i][s], dp[i][subs]+dp[i][s^subs]); } if(dp[i][s]!=inf) q.push(make_pair(-dp[i][s], i)); } dijkstra(s); } printf("%d\n", dp[p[1]][(1<<k)-1]); return 0; }
例题 2019南昌邀请赛A
给出一张图,求让 \(4\) 对点相互可以到达的最小边权值。仅要求一对之间,一对与另外一对可到达也可不到达。
先对于8个点求出最小斯坦纳树,然后求出\(res[s] = \min_{i\in [1,n]} dp[i][s]\) ,表示点集为 \(s\) 的最小斯坦纳树路径和。有些 \(s\) 是不合法的,因为它不能完整的包含4对点中的某一对(如果包含某个点,则必须包含该点的配对点,否则就不包含)。去除这些不合法的集合 \(s\), 在合法的集合之间进行状压\(dp\)转移即可。
const int N = 30 + 5; const int M = 2000; int n, m, k, tot, cnt; int head[N], ver[M], nxt[M], edge[M], dp[N][(1<<8)], res[1<<8]; int p[N], vis[N]; map<string,int> mp; priority_queue<pair<int,int>> q; void add(int x, int y, int z){ ver[++tot] = y, edge[tot] = z, nxt[tot] = head[x], head[x] = tot; } int getId(string str){ if(mp.count(str))return mp[str]; return mp[str] = ++cnt; } void dijkstra(int s){ memset(vis, 0, sizeof vis); while(q.size()){ auto t = q.top();q.pop(); int x = t.second; if(vis[x]) continue; vis[x] = 1; for(int i=head[x];i;i=nxt[i]){ int y = ver[i]; if(dp[y][s] > dp[x][s] + edge[i]){ dp[y][s] = dp[x][s] + edge[i]; q.push(make_pair(-dp[y][s], y)); } } } } bool check(int s){ for(int i=0;i<8;i+=2){ if((s >> i & 1) != (s >> (i+1) & 1)) return false; } return true; } int main(){ scanf("%d%d", &n, &m); for(int i=1;i<=n;i++){ string str;cin >> str; getId(str); } for(int i=1;i<=m;i++){ int x, y, z; string s, t; cin >> s >> t >> z; x = getId(s); y = getId(t); add(x, y, z);add(y, x, z); } for(int i=1;i<=4;i++){ string s, t; cin >> s >> t; p[i*2-1] = getId(s); p[i*2] = getId(t); } k = 8; memset(dp, 0x3f, sizeof dp); for(int i=1;i<=k;i++){ dp[p[i]][1<<(i-1)] = 0; } for(int S = 1; S < (1 << k); S++){ for(int i=1; i <= n; i++){ for(int subs = (S-1)&S; subs; subs = S & (subs-1)){ dp[i][S] = min(dp[i][S], dp[i][subs]+dp[i][S^subs]); } if(dp[i][S]!=inf) q.push(make_pair(-dp[i][S], i)); } dijkstra(S); } //以上为斯坦纳树模板 memset(res, 0x3f, sizeof res); for(int s = 1; s < (1<<k);s++){ if(!check(s)) continue; // 不合法 s 直接跳过 for(int i=1;i<=n;i++){ res[s] = min(res[s], dp[i][s]); //更新得到res[s] } } for(int s = 1; s < (1 << k); s++){ for(int subs=(s-1)&s;subs;subs=(subs-1)&s){ res[s] = min(res[s], res[subs] + res[s^subs]); //不合法的s保证不会发生转移,因为提前赋值为inf } } printf("%d\n", res[(1<<k)-1]); return 0; }
来源:https://www.cnblogs.com/1625--H/p/12580187.html