Solution -「CEOI2017」MUSEUM
因为网上的题解只有两篇且都是在 CSDN 垃圾桶里的并且写得十分冗长,这里更新一发好写的题解。
题目描述应该很像背包,需要选 \(k\) 个点出来,但是稍有不同的是这里并不仅仅局限于走子树,也可以走回来。于是直接把这种情况也设出来。定义 \(f_{i, j, 0 / 1}\) 表示现在在以 \(i\) 为根的子树中,已经走了 \(j\) 个点,\(0\) 表示没有回到 \(i\) 点,\(1\) 表示回到了 \(i\) 点。这样 01 联动就能更新出来想要的了。
状态转移。其实是用类似于树上背包的东西更新的,\(v\) 是 \(u\) 的儿子。所以 \(f_{u, i, 0}\) 会表示的是目前更新到的 \(u\) 的子树部分,\(f_{v, j, 0}\) 则是即将更新的部分。对于 \(f_{u, i + j, 0}\),要么先走 \(u\) 部分的子树并回到 \(u\),再走 \(u \to v\),最后走以 \(v\) 为根的子树不用回来;要么先走 \(u \to v\),走以 \(v\) 为根的子树并回来,再走 \(v \to u\),最后走 \(u\) 部分子树不用回来。\(f_{u, i + j, 1}\) 要回来,那么就是 \(u\) 部分子树走回来以及以 \(v\) 为根的子树走回来,加上 \(u \to v\) 和 \(v \to u\)。
普通的树上背包更新方式保证 \(\mathcal O(n ^ 2)\)。
方程懒得打,直接看代码。
namespace liuzimingc {
const int N = 1e4 + 5;
int n, k, x, f[N][N][2], siz[N];
vector<pair<int, int>> e[N];
void dfs(int u, int fa) {
f[u][0][0] = f[u][0][1] = f[u][1][0] = f[u][1][1] = 0;
siz[u] = 1;
for (const auto &i : e[u]) {
int v = i.first, w = i.second;
if (v == fa) continue;
dfs(v, u);
for (int i = siz[u]; ~i; i--)
for (int j = min(siz[v], k - i); j >= 0; j--) {
f[u][i + j][0] = min(f[u][i + j][0], f[u][i][1] + w + f[v][j][0]);
f[u][i + j][0] = min(f[u][i + j][0], f[u][i][0] + w * 2 + f[v][j][1]);
f[u][i + j][1] = min(f[u][i + j][1], f[u][i][1] + w * 2 + f[v][j][1]);
}
siz[u] += siz[v]; // 普通的树上背包更新方式保证 O(n ^ 2)
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> n >> k >> x;
for (int i = 1; i < n; i++) {
int u, v, w;
cin >> u >> v >> w;
e[u].push_back(make_pair(v, w));
e[v].push_back(make_pair(u, w));
}
memset(f, 0x3f, sizeof(f));
dfs(x, 0);
cout << min(f[x][k][0], f[x][k][1]) << endl;
return 0;
}
} // namespace liuzimingc
Posted by liuzimingc