树上最小点覆盖的一类问题

前言

关于下文中 \(lim\) 较小的最小点覆盖问题,我们通常会对每个节点设出若干状态转移,而下文所说的问题是此问题的通解,但复杂度为平方级别

题意

给定一棵无根有权树,每个点建消防站都有一定代价 \(c\),每个点都有一个限制 \(lim\),表示离它最近的消防站的最大距离。求让所有点安全的最小代价。

分析

我们首先考虑对于一个点最在乎的是什么,发现其实是离他最近的且建设消防站的点。

既然这个状态如此重要,而且似乎直接表示出来非常的困难。我们考虑直接将其加入状态里。

设置动态规划状态

\(dp_{u,i}\)\(u\) 这个点离他最近的且建设消防站的点为 \(i\)\(u\) 子树内结点全部安全。

转移

在树形 \(dp\) 的转移时,我们通常用父亲与儿子的关系去转移,我们在这里也是一样的。

  1. \(u,v\) 两点最近点不相同考虑直接转移即可。

  2. \(u,v\) 两点最近点相同,发现会重复统计一次贡献,减去即可。

其中第 1 中转移开个 \(min\) 数组优化即可。

代码

void dfs1(int u, int par) {
//	Debug(u);
	for (auto [v, w] : G[u]) {
		if (v == par)
			continue;
		dis[v] = dis[u] + w;
		dfs1(v, u);
	}
}

void dfs(int u, int par) {
//	Debug(u);
	for (int i = 1; i <= n; i++) {
		if (can[u][i])
			dp[u][i] = c[i];
		else
			dp[u][i] = inf;
	}
	for (auto [v, w] : G[u]) {
		if (v == par)
			continue;
		dfs(v, u);
		for (int i = 1; i <= n; i++)
			dp[u][i] += min(f[v], dp[v][i] - c[i]);
	}
	f[u] = *min_element(dp[u] + 1, dp[u] + n + 1);
}

void init(int n) {
	for (int i = 1; i <= n; i++) {
		G[i].clear();
		for(int j=1;j<=n;j++)
			can[i][j]=0;
	}
		
}

void Main() {
	n = rd;
	init(n);
	for (int i = 1; i <= n; i++)
		c[i] = rd;
	for (int i = 1; i <= n; i++)
		d[i] = rd;
	for (int i = 1; i < n; i++) {
		int u = rd, v = rd, w = rd;
		G[u].pb({v, w});
		G[v].pb({u, w});
	}
	for (int i = 1; i <= n; i++) {
		dis[i] = 0, dfs1(i, 0);
		for (int j = 1; j <= n; j++)
			if (dis[j] <= d[i])
				can[i][j] = 1;
	}
	dfs(1, 0);
	cout << f[1] << endl;
}
posted @ 2024-04-23 21:20  SmileMask  阅读(38)  评论(1编辑  收藏  举报