【BZOJ2595_洛谷4294】[WC2008]游览计划(斯坦纳树_状压DP)

上个月写的题qwq……突然想写篇博客

题目:

洛谷4294

分析:

斯坦纳树模板题。

简单来说,斯坦纳树问题就是给定一张有边权(或点权)的无向图,要求选若干条边使图中一些选定的点连通(可以经过其他点),且边权(或点权)之和最小。很明显,这样最终形成的是一棵树。

通常,斯坦纳树问题规模都比较小。考虑状压DP。用\(dp[u][S]\)表示让点\(u\)与集合\(S\)中所有关键点连通的最小花费。有如下两种转移:

第一,把两条到\(u\)的路径拼在一起,减去重合点\(u\)的点权,即(\(w_u\)表示点\(u\)的点权,\(S'\)表示\(S\)的一个真非空子集,\(S-S'\)表示以\(S'\)相对于\(S\)的补集,下同):

\[dp[u][S]=min(dp[u][S']+dp[u][S-S']-w_u) \]

第二,延伸一条路径,即(\(v\)\(u\)之间存在一条边):

\[dp[u][S]=min(dp[v][S]+w_u) \]

第二种存在循环更新的问题。但是它长得很像最短路,于是大力跑最短路算法即可。

注意更新顺序,要从小到大枚举集合\(S\),先更新第一种再更新第二种。

代码:

把上面的点\(u\)换成坐标就好了……

dp的时候记一下从哪个状态转移来的。

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cctype>
#include <queue>
#include <functional>
using namespace std;
#define _ 0

namespace zyt
{
	template<typename T>
	inline void read(T &x)
	{
		char c;
		bool f = false;
		x = 0;
		do
			c = getchar();
		while (c != '-' && !isdigit(c));
		if (c == '-')
			f = true, c = getchar();
		do
			x = x * 10 + c - '0', c = getchar();
		while (isdigit(c));
		if (f)
			x = -x;
	}
	template<typename T>
	inline void write(T x)
	{
		static char buf[20];
		char *pos = buf;
		if (x < 0)
			putchar('-'), x = -x;
		do
			*pos++ = x % 10 + '0';
		while (x /= 10);
		while (pos > buf)
			putchar(*--pos);
	}
	const int N = 10, ST = 1 << N, INF = 0x3f3f3f3f;
	struct _pre
	{
		int x, y, st;
		_pre(const int _x = -1, const int _y = -1, const int _st = -1)
			: x(_x), y(_y), st(_st) {}
	}pre[N][N][ST];
	struct point
	{
		int x, y;
		point(const int _x = 0, const int _y = 0)
			: x(_x), y(_y) {}
		bool operator < (const point &b) const
		{
			return x == b.x ? y < b.y : x < b.x;
		}
	};
	int f[N][N][ST], arr[N][N], n, m, k;
	const int dx[] = {0, 0, 1, -1};
	const int dy[] = {1, -1, 0, 0};
	void Dijkstra(const int s)
	{
		typedef pair<int, point> pip;
		static priority_queue<pip, vector<pip>, greater<pip> > q;
		static bool vis[N][N];
		while (!q.empty())
			q.pop();
		for (int i = 0; i < n; i++)
			for (int j = 0; j < m; j++)
			{
				q.push(make_pair(f[i][j][s], point(i, j)));
				vis[i][j] = false;
			}
		while (!q.empty())
		{
			point u = q.top().second;
			q.pop();
			vis[u.x][u.y] = true;
			for (int i = 0; i < 4; i++)
			{
				point v = point(u.x + dx[i], u.y + dy[i]);
				if (v.x < 0 || v.x >= n || v.y < 0 || v.y >= m || vis[v.x][v.y])
					continue;
				if (f[v.x][v.y][s] > f[u.x][u.y][s] + arr[v.x][v.y])
				{
					f[v.x][v.y][s] = f[u.x][u.y][s] + arr[v.x][v.y];
					pre[v.x][v.y][s] = _pre(u.x, u.y, s);
					q.push(make_pair(f[v.x][v.y][s], v));
				}
			}
		}
	}
	bool mark[N][N];
	void dfs(const _pre p)
	{
		mark[p.x][p.y] = true;
		_pre nxt = pre[p.x][p.y][p.st];
		if (nxt.x == -1 && nxt.y == -1 && nxt.st == -1)
			return;
		dfs(nxt);
		if (nxt.st != p.st)
		{
			nxt.st ^= p.st;
			dfs(nxt);
		}
	}
	int work()
	{
		read(n), read(m);
		for (int i = 0; i < n; i++)
		{
			memset(f[i], INF, sizeof(int[m][ST]));
			for (int j = 0; j < m; j++)
			{
				read(arr[i][j]);
				if (!arr[i][j])
					f[i][j][1 << (k++)] = 0;
			}
		}
		for (int i = 0; i < (1 << k); i++)
		{
			for (int j = (i - 1) & i; j; j = (j - 1) & i)
				for (int x = 0; x < n; x++)
					for (int y = 0; y < m; y++)
						if (f[x][y][i] > f[x][y][j] + f[x][y][i ^ j] - arr[x][y])
						{
							f[x][y][i] = f[x][y][j] + f[x][y][i ^ j] - arr[x][y];
							pre[x][y][i] = _pre(x, y, j);
						}
			Dijkstra(i);
		}
		int ans = INF;
		_pre pans;
		for (int i = 0; i < n; i++)
			for (int j = 0; j < m; j++)
				if (ans > f[i][j][(1 << k) - 1])
				{
					ans = f[i][j][(1 << k) - 1];
					pans = _pre(i, j, (1 << k) - 1);
				}
		write(ans), putchar('\n');
		dfs(pans);
		for (int i = 0; i < n; i++)
		{
			for (int j = 0; j < m; j++)
				if (!arr[i][j])
					putchar('x');
				else if (mark[i][j])
					putchar('o');
				else
					putchar('_');
			putchar('\n');
		}
		return (0^_^0);
	}	
}
int main()	
{
	return zyt::work();
}
posted @ 2019-01-10 09:07  Inspector_Javert  阅读(76)  评论(0编辑  收藏  举报