国庆集训 树形DP

P2279 [HNOI2003] 消防局的设立

首先定义状态

由于每个节点 \(i\) 都可以被自身以及\(fa[i] \ fa[fa[i]]\ son[i]\ son\ [son[i]]\) 管理,所以需要定义 \(f(i,k)\) 表示第 \(i\) 个节点被第 \(k\) 种状态管理的最小花费和,0表示自己、0表示向上2个、1表示向上1个、2表示自己、3表示向下1个,4表示向下2个
注:此处1个 2个并不是指的父亲(儿子)或爷爷(孙子)建立站台,而是可以从本节点向上1/2/-1/-2个节点可以被覆盖到

初始状态

\(i\) 节点不是叶子节点则全初始化为0
\(i\) 是叶子节点,则无法被儿子管理,只能被自己和父亲管理,则 \(f(i,0)=1\)同时 \(f(i,1)=1,f(1,2)=1\)

状态转移

对于 \(f(i,0)=\sum\limits _{j是i的son}f(j,4)+1\)
因为想要从本节点覆盖到向上两个,则必须是从本节点建站,而想要建站数量最小,则站点分布最稀疏,本站点可以覆盖到自身的孩子,所以让孩子覆盖最远就最稀疏,所以最小了
对于\(f(i,1)=\min(\min\limits_{j是i的儿子}(f(j,0)+\sum\limits_{y是i的儿子且y\neq j}f(y,3),f(i,0))\)
如果刚好向上覆盖一个到则必须有一个子节点是消防站,其他节点随便取,为了最小,则其他节点取f(y,3)
如果多覆盖一层,就是向上覆盖2层,和前一个状态一样
对于\(f(i,2)=\min(\min\limits_{j是i的儿子}(f(j,1)+\sum\limits_{y是i的儿子且y\neq j}f(y, 2),f(i,1))\)
如果是是刚好覆盖到自己则是从其中任意一个儿子向上一个,其他孩子随便取1-2之间,但是为了最少则取2
如果覆盖到了自己上面一层,就和 \(f(i,0)\) 一样
对于 \(f(i,3)=\min(\sum\limits_{j是i的儿子}f(j,2),f(i,2))\)
如果向下覆盖一层,则子节点建站
否则父节点建站
懒得写了 同理:)
对于 \(f(i,4)=\min(\sum\limits_{j是i的儿子}f(j,3),f(i,3))\)
如果向下覆盖二层,则孙子节点建站
否则儿子节点建站

代码

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+5;
int n, x;
vector<int >G[N];
int f[N][5];
void dfs(int x, int fa) {
	f[x][0] = 1;
	f[x][3] = f[x][4] = 0;
	for (auto y : G[x]) {
		if (y == fa)continue;
		dfs(y, x);
		f[x][0] += f[y][4];
		f[x][4] += f[y][3];
		f[x][3] += f[y][2];
	}
	if (G[x].size() == 0) {
		f[x][1] = f[x][2] = 1;
	} else {
		f[x][1] = f[x][2] = 1e9;
		int f1, f2;
		for (auto y : G [x]) {
			f1 = f[y][0];
			f2 = f[y][1];
			for (auto yy : G[x]) {
				if (y == yy)continue;
				f1 += f[yy][3];
				f2 += f[yy][2];
			}
			f[x][1] = min(f[x][1], f1);
			f[x][2] = min(f[x][2], f2);
		}
	}
	for (int i = 1; i <= 4; i++) {
		f[x][i] = min(f[x][i - 1], f[x][i]);
	}
}
int main() {
	cin >> n;
	for (int i = 2; i <= n; i++) {
		cin >> x;
		G[x].push_back(i);
		//G[i].push_back(x);
	}
	dfs(1, 0);
	cout << f[1][2];
	return 0;
}
posted @ 2025-10-06 18:55  ppi_SAMA  阅读(6)  评论(0)    收藏  举报