题解 bzoj3836 [Poi2014] Tourism

题目传送门

Analysis

显然是个 NP 问题,然而题目保证「任意两点间不存在节点数超过 \(10\) 的简单路径」,就有了一定可操作性。

先考虑图为树的简化版。就是一个最小覆盖集的问题。经典的树形 dp,用 \(dp_{i,s=\{0/1/2\}}\) 表示满足 \(i\) 的子树(不包含 \(i\))被完全覆盖,选择了 \(i\)、未选 \(i\) 且未被覆盖、未选 \(i\) 但已经被覆盖的情况。事实上就是简单的状压。

由于每个连通快的搜索树的深度最大只有 \(10\),且非树边一定是 Back edge,因此同样可以考虑状压。同样记 \(dp_{i,s}\) 表示 \(i\) 点的状态。由于从树上问题变为图上问题,一个点同时会被其子树中的点影响,也会被其祖先影响,因此这里 \(dp\) 的定义有一点点不同。

定义 \(dp_{i,s}\) 为,满足 \(i\) 的“子树”被覆盖(不包含 \(i\)),且 dfs 序比 \(i\) 小的点被覆盖(不包含 \(i\) 到“根”路径上的点),\(i\) 到“根”的路径上每个点的状态为 \(s\) 的最小代价。状态 \(s\) 中,其第 \(j\) 位为 \(i\) 到根的路径上深度为 \(j\) 的点的状态。与树上的情形类似,状态有三种:

1.选了 \(i\)

2.没选 \(i\),且 \(i\) 未被覆盖;

3.没选 \(i\),且 \(i\) 已被覆盖。

由于祖先和儿子相互影响,每个点的状态需要更新两次。即 \(father \to son\)\(son\to father\)。首先用父亲来更新儿子。

\(i\) 不选,若有直接与 \(i\) 相连且已经选了的点,那么有:

\[dp_{fa_i,s}\to dp_{i,s+2\times 3^{dep_i}} \]

否则:

\[dp_{fa_i,s}\to dp_{i,s+3^{dep_i}} \]

\(i\) 选,由于存在 Back edge,\(i\) 到“根”的路径上一些点的状态可能由 \(1\) 变到 \(2\)。我们记新的状态为 \(s'\),则:

\[dp_{fa_i,s}+c_i \to dp_{i,s'} \]

剩下的就是 \(i\) 的“子树”对 \(i\) 的影响了。由于要先统计“子树”的状态,需要在递归完每个“子树”时将 \(dp_i\) 从“子树”更新一次:

\[dp_{i,s}=\min(dp_{i,s},dp_{son_i,s+2\times 3^{dep_i}}) \]

在以上过程中,每个“子树”会先继承“祖先”的贡献,然后再重新反过来更新“父亲”。这样就处理了“子树”对 \(i\) 到“根”的路径的贡献。

最后一点就是优化空间。注意到以上方法的空间是 \(\mathcal{O}(3^{10}\times n)\) 的,显然会挂。我们将第一维改为点在搜索树中的深度,空间优化至 \(\mathcal{O}(10\times 3^{10})\)

时间复杂度为 \(\mathcal{O}(10\times 3^{10} \times n)\)

code

#include <bits/stdc++.h>
#define ll long long
#define reg register
#define F(i,a,b) for(reg int i=(a);i<=(b);++i)
#define For(i,x) for(reg int i=head[(x)];i;i=net[(i)])
using namespace std;
bool beginning;
inline int read();
const int N = 2e4 + 5, E = 6e4 + 5, inf = 0x3f3f3f3f;
int n, m, c[N], dep[N];
int ver[E], net[E], head[N], tot;
inline void add(int x, int y) {
	ver[++tot] = y, net[tot] = head[x], head[x] = tot;
}
bool vis[N];
int pw[20] {1}, dp[15][E];
#define bit(x,i) (((x)/pw[(i)])%3)
#define Min(x,y) ((x)>(y) and ((x)=(y)))
void dfs(int x) {

	vis[x] = true;
	memset(dp[dep[x]], 0x3f, sizeof(dp[dep[x]]));

	if (dep[x] == 0) {

		dp[0][0] = c[x];
		dp[0][1] = 0;
		dp[0][2] = inf;
	} else {

		static bool ok[N];
		memset(ok, 0, sizeof(ok));

		For(i, x) {
			if (vis[ver[i]])
				ok[dep[ver[i]]] = true;
		}

		F(s, 0, pw[dep[x]] - 1) {

			if (dp[dep[x] - 1][s] < inf) {

				bool hav = false;
				int sta = s;

				F(i, 0, dep[x] - 1) {
					hav |= ok[i] and bit(s, i) == 0;
					sta += pw[i] * (ok[i] and bit(s, i) == 1);
				}

				if (hav)
					Min(dp[dep[x]][s + pw[dep[x]] * 2], dp[dep[x] - 1][s]);

				else
					Min(dp[dep[x]][s + pw[dep[x]]], dp[dep[x] - 1][s]);

				Min(dp[dep[x]][sta], dp[dep[x] - 1][s] + c[x]);
			}

		}
	}

	For(i, x) {

		if (vis[ver[i]])
			continue;

		dep[ver[i]] = dep[x] + 1;
		dfs(ver[i]);
		F(s, 0, pw[dep[x] + 1] - 1) {
			dp[dep[x]][s] = min(dp[dep[x] + 1][s], dp[dep[x] + 1][s + pw[dep[x] + 1] * 2]);
		}

	}

}
bool ending;
int main() {
	//  printf("%.2lfMB\n",1.0*(&beginning-&ending)/1024/1024);
	n = read(), m = read();
	F(i, 1, n)c[i] = read();
	int x, y;
	F(i, 1, m) {
		x = read(), y = read();
		add(x, y), add(y, x);
	}

	F(i, 1, 10)pw[i] = pw[i - 1] * 3;

	int ans = 0;

	F(i, 1, n)if (!vis[i])
		dfs(i), ans += min(dp[0][0], dp[0][2]);

	printf("%d", ans);
	return 0;
}
inline int read() {
	reg int x = 0;
	reg char c = getchar();

	while (!isdigit(c))
		c = getchar();

	while (isdigit(c))
		x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();

	return x;
}
posted @ 2021-09-18 16:02  Maplisky  阅读(32)  评论(0编辑  收藏  举报