Luogu4294 【WC2008】游览计划

斯坦纳树(我也不知道为什么叫这个名字)是一种状压dp的套路,求在无向带花连通图中,选取边使一些特殊点连通起来的最小花费。

具体到这题就是这样的,设(f_{u,S})表示当前根是(u),与它连通的特殊点的集合是(S)的最小花费。

首先要合并自身的集合。

[f_{u,S}=min_{Tsubseteq S}(f_{u,T}+f_{u,S-T}-a_u) ]

还有换根。

[f_{u,S}=min_{(u,v)in E}(f_{v,S}+a_u) ]

这个东西可以用spfa来转移。

#include<bits/stdc++.h>
#define Rint register int
#define MP make_pair
#define fi first
#define se second
using namespace std;
typedef pair<int, int> pii;
const int N = 11, M = 103, d[2][4] = {{0, 1, 0, -1}, {1, 0, -1, 0}}, inf = 0x3f3f3f3f;
int n, m, tot, a[N][N], f[N][N][1024], front, rear;
struct Node {
	int x, y, S;
} pre[N][N][1024];
pii q[M];
bool inq[N][N];
inline void AMOD(int &x){++ x; if(x == M) x = 0;}
inline void spfa(int S){
	while(front != rear){
		pii now = q[front]; AMOD(front);
		int ox = now.fi, oy = now.se; inq[ox][oy] = false;
		for(Rint i = 0;i < 4;i ++){
			int nx = ox + d[0][i], ny = oy + d[1][i];
			if(nx >= 1 && nx <= n && ny >= 1 && ny <= m && f[nx][ny][S] > f[ox][oy][S] + a[nx][ny]){
				f[nx][ny][S] = f[ox][oy][S] + a[nx][ny];
				pre[nx][ny][S] = (Node){ox, oy, S};
				if(!inq[nx][ny]){inq[nx][ny] = true; q[rear] = MP(nx, ny); AMOD(rear);}
			}
		}
	}
}
bool vis[N][N];
inline void dfs(int x, int y, int S){
	vis[x][y] = true;
	Node tmp = pre[x][y][S];
	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, S - tmp.S);
}
int main(){
	scanf("%d%d", &n, &m);
	memset(f, 0x3f, sizeof f);
	for(Rint i = 1;i <= n;i ++)
		for(Rint j = 1;j <= m;j ++){
			scanf("%d", a[i] + j);
			if(!a[i][j]) f[i][j][1 << tot] = 0, ++ tot;
		}
	int lim = 1 << tot;
	for(Rint S = 0;S < lim;S ++){
		front = rear = 0;
		for(Rint i = 1;i <= n;i ++)
			for(Rint j = 1;j <= m;j ++){
				for(Rint SS = S;SS;SS = (SS - 1) & S)
					if(f[i][j][S] > f[i][j][SS] + f[i][j][S - SS] - a[i][j]){
						f[i][j][S] = f[i][j][SS] + f[i][j][S - SS] - a[i][j];
						pre[i][j][S] = (Node){i, j, SS};
					}
				if(f[i][j][S] < inf) q[rear ++] = MP(i, j), inq[i][j] = true;
			}
		spfa(S);
	}
	int ansx = 0, ansy = 0;
	bool flag = true;
	for(Rint i = 1;flag && i <= n;i ++)
		for(Rint j = 1;flag && j <= m;j ++)
			if(!a[i][j]){ansx = i; ansy = j; flag = false;}
	printf("%d
", f[ansx][ansy][lim - 1]);
	dfs(ansx, ansy, lim - 1);
	for(Rint i = 1;i <= n;i ++){
		for(Rint j = 1;j <= m;j ++)
			if(!a[i][j]) putchar('x');
			else if(vis[i][j]) putchar('o');
			else putchar('_');
		putchar('
');
	}
}
原文地址:https://www.cnblogs.com/AThousandMoons/p/11720997.html