题解 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\) 相连且已经选了的点,那么有:
否则:
若 \(i\) 选,由于存在 Back edge,\(i\) 到“根”的路径上一些点的状态可能由 \(1\) 变到 \(2\)。我们记新的状态为 \(s'\),则:
剩下的就是 \(i\) 的“子树”对 \(i\) 的影响了。由于要先统计“子树”的状态,需要在递归完每个“子树”时将 \(dp_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;
}
本文来自博客园,作者:Maplisky,转载请注明原文链接:https://www.cnblogs.com/lbh2021/p/15309076.html