test20260729

\(\text{P3243}\)

乐子签到题,优先队列加拓扑求个字典序最大,反过来就好了

\(\text{P2279}\)

建最少的点,每个点的笼罩范围是距离在2以内,要把所有点都笼罩到

树形\(dp\)
首先你在 \(dp\) 的时候,不用考虑这个点关于父亲,和祖父笼罩到的情况因为父亲和组父回来考虑这个点
所以我们\(dp\) 的时候就可以只考虑子树内的点了

\(f_{u,0/1}\) 表示在 \(u\) 这个点建或者不建的时,使得\(u\) 整个子树都被笼罩到时的最小代价

\(f_{u,1}\)

当在 \(u\) 这个点建设时,对于\(u\) 的子树内部,它可以笼罩到\(u\) 自己,和\(u\)的所有儿子,和孙子
这样的话,笼罩 \(u\) 就划分为两个部分的代价

  • \(u\) 和它的儿子孙子,代价是1,也就是在 \(u\) 这里建一个站
  • 所有\(u\)的曾孙形成的子树,这里的代价记为 \(sss_i\)
    表示使得 \(i\) 的所有曾孙形成的子树满足的最小代价

    为了转移这个 \(sss_i\),我们再记\(ss_i\) 表示\(i\) 的所有孙子形成的子树满足条件的最小代价;\(s_i\)表示\(i\)的所有儿子形成的子树满足条件的最小代价
    然后就很容易转移了,详细见于代码

所以 \(f_{u,1} = sss_u + 1\)

\(f_{u, 0}\)

一个点不建,那么为了笼罩这一个点,它的儿子中或者孙子中至少有一个建

  • 至少一个儿子建:记 \(h_i\) 表示至少有一个儿子建了,使得 \(i\) 这个子树都被覆盖到的最小代价
    那么考虑一个点 \(v\in son(u)\),我们再\(v\) 这个点建,那么\(v\)这个点本身可以笼罩到哪些点?v子树内的一部分以及u的所有儿子
    所以我们就把代价分成了两个部分

    • v 子树, u,和所有 \(son(u)\)
      代价:\(min(f_{v,0},f_{v,1})\)
    • 其他部分:u除了v以外的儿子 的儿子构成的子树
      代价:\(\sum_{w\in son(u)且w\ne v}^{s_w}\)

    所以把这个部分取最小值就是 \(h_u\)

  • 至少一个孙子建了
    这里有点绕,有一个孙子建了,也就是 \(u\) 的一个儿子 \(v\) 的至少一个儿子建了

    多读两遍理解一下

    那我们依然枚举这个 \(v\),把代价分为两个部分

    • v子树和u
      在v的一个儿子建,两个距离,也就是刚好向上能找到了u
      那么你看了一下发现 \(v\) 至少一个儿子建的最小值就是 \(h_v\) ! 而我们前面定义 \(h\) 的时候的定义,把整棵子树的代价都算在了\(h\) 里面,所以这里的 \(h_v\) 就已经覆盖到了 v子树和u,这一部分的代价就是 \(h_v\)
    • 剩余u除了v以外的其他儿子 \(w\) 笼罩不到,那么wz覆盖它的子树的最小代价就是 \(min(f_{w,0},f_{w,1})\) (只要求覆盖这一个子树,并没有其他限制)

    孙子建总代价 \(h_v + \sum_{w\in son(u)且w\ne v}^{min(f_{w,0},f_{w,1})}\)

这样就得到了 \(f_{u,0}\) 的转移,就是儿子建和孙子建的最小值

最终的答案就是 \(min(f_{1,0},f_{1,1})\)

code

code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 5010;
int n;
vector<int> G[N];
int fa[N];
int f[N][2], h[N];
int s[N], ss[N], sss[N];
void dfs(int u) {
	h[u] = 1e18;
	f[u][0] = 1e18;
	for(int v : G[u]) {
		if(v == fa[u]) continue;
		dfs(v);
	}
	for(int v : G[u]) { // 儿子建
		if(v == fa[u]) continue;
		int res = 0;
		for(int w : G[u]) {
			if(w == fa[u]) continue;
			if(w == v) continue;
//			res += min(s[w], min(f[w][0], f[w][1]));
			res += s[w];
		}
		h[u] = min(h[u], f[v][1] + res);
	}
	f[u][0] = h[u];
	for(int v : G[u]) { // 孙子建
		if(v == fa[u]) continue;
		int res = 0;
		for(int w : G[u]) {
			if(w == v) continue;
			if(w == fa[u]) continue;
			res += min(f[w][0], f[w][1]);
		}
		f[u][0] = min(f[u][0], h[v] + res);
	}
	s[u] = ss[u] = sss[u] = 0;
	for(int v : G[u]) {
		if(v == fa[u]) continue;
		s[u] = s[u] + min(f[v][0], f[v][1]);
		ss[u] = ss[u] + s[v];
		sss[u] = sss[u] + ss[v];
	}
//	s[u] = min(s[u], h[u]);
f[u][1] = sss[u] + 1;
	s[u] = min(s[u], f[u][0]);
	s[u] = min(s[u], f[u][1]);
	
	ss[u] = min(ss[u], f[u][0]);
	ss[u] = min(ss[u], f[u][1]);
	
	sss[u] = min(ss[u], f[u][0]);
	sss[u] = min(ss[u], f[u][1]);
}
signed main() {
	// freopen("fire.in","r",stdin);
	// freopen("fire.out","w",stdout);
	ios::sync_with_stdio(0);
	cin >> n;
	for(int i = 2; i <= n; i ++) {
		cin >> fa[i];
		G[fa[i]].push_back(i);
	}
	dfs(1);
	cout << min(f[1][0], f[1][1]) << '\n';
//	cout << "Fi0 : ";
//	for(int i = 1; i <= n; i ++) cout << f[i][0] << ' '; cout << '\n';
//	cout << "Fi1 : ";
//	for(int i = 1; i <= n; i ++) cout << f[i][1] << ' '; cout << '\n';
//	cout << "hi : ";
//	for(int i = 1; i <= n; i ++) cout << h[i] << ' '; cout << '\n';
//	cout << "si : ";
//	for(int i = 1; i <= n; i ++) cout << s[i] << ' '; cout << '\n';
//	cout << "ssi : ";
//	for(int i = 1; i <= n; i ++) cout << ss[i] << ' '; cout << '\n';
//	cout << "sssi : ";
//	for(int i = 1; i <= n; i ++) cout << sss[i] << ' '; cout << '\n';
	return 0;
}
posted @ 2026-07-30 22:51  wmq2012  阅读(8)  评论(0)    收藏  举报