最小斯坦纳树
问题描述:
给定一个包含 (n) 个结点和 (m) 条带权边的无向连通图 (G=(V,E))。
再给定包含 (k) 个结点的点集 (S),选出 (G) 的子图 (G'=(V',E')) 使得:
-
(Ssubseteq 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(mlog m imes 2^k))
第二类转移复杂度为(O(n imes 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
", dp[p[1]][(1<<k)-1]);
return 0;
}
例题 2019南昌邀请赛A
给出一张图,求让 (4) 对点相互可以到达的最小边权值。仅要求一对之间,一对与另外一对可到达也可不到达。
先对于8个点求出最小斯坦纳树,然后求出(res[s] = min_{iin [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
", res[(1<<k)-1]);
return 0;
}