斯坦纳树裸题
但是本题的斯坦纳树是点权,与大多数图的边权不同,下边讲解一般的边权做法,然后再将本题的题解。
斯坦纳树:给出一些点,选出若干条边使得这些点连通,求总边权的最值。
斯坦纳树是NP问题,不存在多项式时间内的解法,求解方法是状压dp。
设 f[i][j]表示选择若干条边,使得状态为 ii 的给定点连通,并且当前可以选择下一条边的端点为 jj 的最小边权和。初始状态 f[2k][pos[k]]=0,其中 pos[k] 为第 kk 个给定点的编号。
那么我们对于每个 i 和 j ,首先枚举 i的子集 k ,用 f[k][j]+f[i−k][j] 更新 f[i][j]f[i][j] 。
然后再考虑同层转移:如果 x 与 y 边权为 z ,用f[i][x]+z 更新f[i][y] ,用f[i][y] 更新 f[i][x] 。容易发现这个转移就是最短路,因此使用堆优化Dijkstra跑一遍得出所有的 f[i][j] 。
最终答案就是 min{f[2p−1][i]}
这个dp的理解比较显然,时间复杂度O(3p·n+2p·mlogn) ,其中 p 是给定点的数目。
那么对于本题,由于权值在点上,因此枚举子集转移时,当前点会重复选择,需要减去代价。
在同层转移时,求的就是点权最短路,边长为目标点的点权。
设 f[i][j][k]表示选定若干条边,使得状态为 ii 的给定点连通,并且当前可以选择下一条边的相邻点为 (j,k)(j,k) 的最小点权和。初始状态 f[2k][posx[k]][posy[k]]=0 .
首先枚举子集转移:f[i][j][k]=minl⊆if[l][j][k]+f[i−l][j][k]−a[j][k] ,然后同层转移,与 (j,k)相邻的点的 f 加上 a[j][k] 可以转移到f[i][j][k] 。
输出方案的话直接记录路径,记录从哪种方式的哪个状态转移过来,dfs一遍即可知道选定的点。
时间复杂度 O(3p⋅nm+2p⋅nmlogn)
/*
斯坦纳树
*/
#include <bits/stdc++.h>
#define mp make_pair
#define fi first
#define se second
#define PII pair<int,int>
using namespace std;
const int maxn = 1050;
const int INF = 1e9;
int N, M, tot = 0 ;
int a[12][12], f[12][12][maxn];
int dir[4][2] = {{-1,0}, {1,0}, {0,-1}, {0,1}};
int vis[12][12];
struct PRE{
int x, y, S;
}pre[12][12][maxn];
queue<PII> q;
void SPFA(int cur){
while (q.size() != 0){
PII p = q.front();q.pop();
vis[p.fi][p.se] = 0;
for (int i = 0 ; i < 4; i++){
int nxtx = p.fi + dir[i][0], nxty = p.se + dir[i][1];
if (nxtx < 1 || nxtx > N || nxty < 1 || nxty > M) continue;
if (f[nxtx][nxty][cur] > f[p.fi][p.se][cur] + a[nxtx][nxty]){
f[nxtx][nxty][cur] = f[p.fi][p.se][cur] + a[nxtx][nxty];
pre[nxtx][nxty][cur] = (PRE){p.fi, p.se, cur};
if (!vis[nxtx][nxty]){
vis[nxtx][nxty] = 1, q.push(mp(nxtx, nxty));
}
}
}
}
}
void dfs(int x, int y, int now){
vis[x][y] = 1;
PRE tmp = pre[x][y][now];
if (tmp.x == 0 && tmp.y == 0) return ;
dfs(tmp.x, tmp.y, tmp.S);
if (tmp.x == x && tmp.y == y) dfs(tmp.x, tmp.y, now - tmp.S);
}
int main(){
scanf("%d%d", &N, &M);
memset(f, 0x3f, sizeof(f));
for (int i = 1; i <= N; i++)
for (int j = 1; j <= M; j++){
scanf("%d", &a[i][j]);
if (a[i][j] == 0) f[i][j][1<<tot] = 0, tot++;
}
int limit = (1 << tot) - 1;
for (int sta = 0; sta <= limit; sta++){
for (int i = 1; i <= N; i++)
for (int j = 1; j <= M; j++){
for (int s = sta & (sta - 1); s ; s = (s - 1) & sta){
if (f[i][j][s] + f[i][j][sta - s] - a[i][j] < f[i][j][sta]){
f[i][j][sta] = f[i][j][s] + f[i][j][sta - s] - a[i][j];
pre[i][j][sta] = (PRE){i,j,s};
}
}
if (f[i][j][sta] < INF) q.push(mp(i,j)), vis[i][j] = 1;
}
SPFA(sta);
}
int ansx, ansy, flag = 0;
for (int i = 1; i <= N && !flag; i++)
for (int j = 1; j <= M; j++)
if (!a[i][j]){
ansx = i;
ansy = j;
flag = 1;
break;
}
printf("%d\n", f[ansx][ansy][limit]);
memset(vis, 0, sizeof(vis));
dfs(ansx, ansy, limit);
for (int i = 1; i <= N; i++, puts("")) {
for (int j = 1; j <= M; j++){
if (a[i][j] == 0) putchar('x');
else if (vis[i][j]) putchar('o');
else putchar('_');
}
}
return 0;
}