P6192 【模板】最小斯坦纳树
题面:
题目描述
给定一个包含 n n n 个结点和 m m m 条带权边的无向连通图 G = ( V , E ) G=(V,E) G=(V,E)。
再给定包含 k k k 个结点的点集 S S S,选出 G G G 的子图 G ′ = ( V ′ , E ′ ) G'=(V',E') G′=(V′,E′),使得:
S ⊆ V ′ S\subseteq V' S⊆V′;
G ′ G' G′ 为连通图;
E ′ E' E′ 中所有边的权值和最小。
你只需要求出 E ′ E' E′ 中所有边的权值和。
输入格式
第一行:三个整数 n , m , k n,m,k n,m,k,表示 G G G 的结点数、边数和 S S S 的大小。
接下来 m m m 行:每行三个整数 u , v , w u,v,w u,v,w,表示编号为 u , v u,v u,v 的点之间有一条权值为 w w w 的无向边。
接下来一行: k k k 个互不相同的正整数,表示 S S S 的元素。
输出格式
第一行:一个整数,表示 E ′ E' E′ 中边权和的最小值。
样例 #1
样例输入 #1
7 7 4 1 2 3 2 3 2 4 3 9 2 6 2 4 5 3 6 5 2 7 6 4 2 4 7 5
样例输出 #1
11
提示
【样例解释】
样例中给出的图如下图所示,红色点为 S S S 中的元素,红色边为 E ′ E' E′ 的元素,此时 E ′ E' E′ 中所有边的权值和为 2 + 2 + 3 + 4 = 11 2+2+3+4=11 2+2+3+4=11,达到最小值。
【数据范围】
对于 100 % 100\% 100% 的数据, 1 ≤ n ≤ 100 , 1 ≤ m ≤ 500 , 1 ≤ k ≤ 10 , 1 ≤ u , v ≤ n , 1 ≤ w ≤ 1 0 6 1\leq n\leq 100,\ \ 1\leq m\leq 500,\ \ 1\leq k\leq 10,\ \ 1\leq u,v\leq n,\ \ 1\leq w\leq 10^6 1≤n≤100, 1≤m≤500, 1≤k≤10, 1≤u,v≤n, 1≤w≤106。
保证给出的无向图连通,但 可能 存在重边和自环。
考虑状态压缩,我们设 f S , i f_{S,i} fS,i 为经过了 S S S 中的点,以 i i i 为结尾的最小代价。
每个点 u u u 对应编号 i d u id_u idu,有 f 2 i d u , u = 0 f_{2^{id_u},u} = 0 f2idu,u=0。
因为答案一定是一颗树,故讨论一下:
- 子节点只有一个,则 f S , u ← f S , v + w ( u , v ) f_{S,u} \leftarrow f_{S,v} + w(u,v) fS,u←fS,v+w(u,v)
- 子节点大于一个,则 f S , u ← f T , u + f S ⊕ T , u f_{S,u} \leftarrow f_{T,u} + f_{S\oplus T,u} fS,u←fT,u+fS⊕T,u
第 1 1 1 中情况,我们可以用最短路解决;第 2 2 2 中情况可以用状态压缩技巧:枚举子集解决!
最后求 min i = 0 n − 1 f 2 k − 1 , i \min_{i = 0}^{n - 1} f_{2_{k} - 1,i} mini=0n−1f2k−1,i 即可。
AC-code:
#include<bits/stdc++.h>
using namespace std;
int rd() {
int x = 0, w = 1;
char ch = 0;
while (ch < '0' || ch > '9') {
if (ch == '-') w = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = x * 10 + (ch - '0');
ch = getchar();
}
return x * w;
}
void wt(int x) {
static int sta[35];
int f = 1;
if(x < 0) f = -1,x *= f;
int top = 0;
do {
sta[top++] = x % 10, x /= 10;
} while (x);
if(f == -1) putchar('-');
while (top) putchar(sta[--top] + 48);
}
int n,m,k,dp[1024][101],vis[101];
int head[101],nxt[1005],to[1005],val[1005],cnt;
void init() {memset(head,-1,sizeof(head));cnt = 0;}
void add(int u,int v,int w) {
nxt[cnt] = head[u];
to[cnt] = v;
val[cnt] = w;
head[u] = cnt++;
}
void spfa(int S) {
queue<int> q;
for(int i = 0;i<n;i++)
if(dp[S][i] != 0x3f3f3f3f)
q.emplace(i),vis[i] = true;
while(!q.empty()) {
int c = q.front();
q.pop();
vis[c] = false;
for(int i = head[c];~i;i = nxt[i]) {
int y = to[i],w = val[i];
if(dp[S][y] > dp[S][c] + w) {
dp[S][y] = dp[S][c] + w;
if(vis[y]) continue;
q.emplace(y);vis[y] = true;
}
}
}
}
signed main() {
memset(dp,0x3f,sizeof(dp));
init();
n = rd(),m = rd(),k = rd();
for(int i = 1;i<=m;i++) {
int u = rd(),v = rd(),w = rd();
u--,v--;
add(u,v,w);add(v,u,w);
}
for(int i = 0;i<k;i++) {
int x = rd();x--;
dp[1<<i][x] = 0;
}
for(int S = 1;S < (1<<k);S++) {
for(int T = S & (S - 1);T;T = S & (T - 1)) {
if(T >= (S ^ T))
for(int i = 0;i<n;i++)
dp[S][i] = min(dp[S][i],dp[T][i] + dp[S ^ T][i]);
}
spfa(S);
}
wt(*min_element(dp[(1<<k) - 1],dp[(1<<k) - 1] + n));
return 0;
}