结局是什么,我们自己决定!——明日战记

Solution -「CEOI2017」MUSEUM

因为网上的题解只有两篇且都是在 CSDN 垃圾桶里的并且写得十分冗长,这里更新一发好写的题解。

题目描述应该很像背包,需要选 k 个点出来,但是稍有不同的是这里并不仅仅局限于走子树,也可以走回来。于是直接把这种情况也设出来。定义 fi,j,0/1 表示现在在以 i 为根的子树中,已经走了 j 个点,0 表示没有回到 i 点,1 表示回到了 i 点。这样 01 联动就能更新出来想要的了。

状态转移。其实是用类似于树上背包的东西更新的,vu 的儿子。所以 fu,i,0 会表示的是目前更新到的 u 的子树部分,fv,j,0 则是即将更新的部分。对于 fu,i+j,0,要么先走 u 部分的子树并回到 u,再走 uv,最后走以 v 为根的子树不用回来;要么先走 uv,走以 v 为根的子树并回来,再走 vu,最后走 u 部分子树不用回来。fu,i+j,1 要回来,那么就是 u 部分子树走回来以及以 v 为根的子树走回来,加上 uvvu

普通的树上背包更新方式保证 O(n2)

方程懒得打,直接看代码。

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 @   liuzimingc  阅读(15)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
more_horiz
keyboard_arrow_up light_mode palette
选择主题
点击右上角即可分享
微信分享提示