Solution -「CEOI2017」MUSEUM
因为网上的题解只有两篇且都是在 CSDN 垃圾桶里的并且写得十分冗长,这里更新一发好写的题解。
题目描述应该很像背包,需要选 个点出来,但是稍有不同的是这里并不仅仅局限于走子树,也可以走回来。于是直接把这种情况也设出来。定义 表示现在在以 为根的子树中,已经走了 个点, 表示没有回到 点, 表示回到了 点。这样 01 联动就能更新出来想要的了。
状态转移。其实是用类似于树上背包的东西更新的, 是 的儿子。所以 会表示的是目前更新到的 的子树部分, 则是即将更新的部分。对于 ,要么先走 部分的子树并回到 ,再走 ,最后走以 为根的子树不用回来;要么先走 ,走以 为根的子树并回来,再走 ,最后走 部分子树不用回来。 要回来,那么就是 部分子树走回来以及以 为根的子树走回来,加上 和 。
普通的树上背包更新方式保证 。
方程懒得打,直接看代码。
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
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人