[NOIP2025] 树的价值

https://www.luogu.com.cn/problem/P14637


我们把每一个点看成垫脚石。
对于一个点的mex,首先肯定会从它的某个儿子继承过来,然后再选其他子树里的一些没有用过的垫脚石来给他拔高。
那么这样的继承关系会形成一些链。
我们就相当于给这棵树做了个链剖分。
那我们就从链剖分的角度去看这个题。
首先,如果已经确定了一棵树怎么剖分,怎么算答案。

我们考虑那个绿点如果给最下面的那个红链贡献,会给答案贡献下面红链的长度。
如果给中间那个红链贡献,会给答案贡献中间红链的长度。
对于上面的同理。
所以一个点的最大贡献就是从它到根的最长链长度。
我们为了最大化答案,一定会让它这样贡献。
同时通过调整发现,如果一条剖出来的链有贡献,那么长度肯定链顶到根的最长的链长。
于是我们令 \(f_{x,i}\) 表示到 \(x\),当前链长为 \(i\) 且长度是从其到根的链中最长的,的答案。
\(g_{x,i}\) 表示到 \(x\),当前新开了一条链,且从 \(x\) 到根的链中最长的链长为 \(i\),的答案。
容易得到转移:

\[\begin{align*} f_{x,i}&=\max_{v\in son_x}(f_{v,i+1}+\sum_{w\in son_x,w\not= v}g_{w,i}+i)\\ g_{x,i}&=\max_{dist(v,x)=i-1}(i(i-1)+\sum_{w\in path(x,v),w\not=x}h_{x,i} + f_{v,i}) \end{align*} \]

其中

\[h_{x,i}=\sum_{v\in son_{fa_x},v\not= x}g_{v,i} \]

我们发现对于一个 \(i\) 的转移只跟 \(i\) 和 \(i + 1\) 的答案有关。
所以我们可以在外层枚举一个 \(i\),dfs进行转移即可。
而

\[\sum_{w\in path(x,v),w\not=x}h_{x,i} + f_{v,i} \]

可以用树链剖分优化dp解决,注意要打个tag。

点击查看代码
#include <iostream>
#include <vector>

using std::cin;
using std::cout;
const int N = 8e3 + 10;
typedef long long ll;
const ll oo = 1e15;

int c;
int ws[N];
int siz[N];
int dist[N];
ll h[N];
ll tag[N];
ll g[N][2];
ll f[N][2];
ll t[N << 2], *s[N], *p = t;
std::vector<int> e[N];

void dfs(int x)
{
	siz[x] = 1;
	for (auto to : e[x])
	{	
		dfs(to);
		siz[x] += siz[to];
		if (dist[to] > dist[ws[x]])
			ws[x] = to;
	}
	dist[x] = dist[ws[x]] + 1;
}
void init_dp(int x)
{
	if (ws[x])
	{
		s[ws[x]] = s[x] + 1;
		init_dp(ws[x]);
	}
	for (auto to : e[x])
	{
		if (to != ws[x])
		{
			s[to] = p;
			p += dist[to] + 1;
			init_dp(to);
		}
	}
}
void get_ans(int x)
{
	for (auto to : e[x])
		get_ans(to);
	ll sm = 0;
	for (auto to : e[x])
		sm += g[to][c & 1];
	for (auto to : e[x])
		h[to] = sm - g[to][c & 1];
	f[x][c & 1] = 1ll * c * siz[x];
	g[x][c & 1] = 1ll * c * siz[x];
	tag[x] = tag[ws[x]] + h[ws[x]];
	for (auto to : e[x])
	{
		if (to != ws[x])
		{
			for (int i = 0; i < dist[to]; ++i)
				s[x][i + 1] = std::max(s[x][i + 1], s[to][i] + tag[to] - tag[x] + h[to]);
		}
		f[x][c & 1] = std::max(f[x][c & 1], f[to][(c & 1) ^ 1] + h[to] + c);
	}
	s[x][0] = f[x][c & 1] - tag[x];
	if (c <= dist[x])
		g[x][c & 1] = std::max(g[x][c & 1], 1ll * c * (c - 1) + s[x][c - 1] + tag[x]);
}
void init(int n)
{
	for (int i = 0; i <= (n << 2); ++i)
		t[i] = -oo;
	p = t;
	for (int i = 1; i <= n; ++i)
	{
		e[i].clear();
		ws[i] = dist[i] = siz[i] = 0;
		h[i] = f[i][0] = f[i][1] = g[i][0] = g[i][1] = tag[i] = 0;
	}
}
void solve()
{
	int n, m;
	cin >> n >> m;
	for (int i = 2; i <= n; ++i)
	{
		int f;
		cin >> f;
		e[f].push_back(i);
	}
	dfs(1);
	s[1] = p;
	p += dist[1] + 1;
	init_dp(1);
	for (c = m + 1; c >= 1; --c)
	{
		for (int j = 0; j <= (n << 2); ++j)
			t[j] = -oo;
		get_ans(1);
		if (c == 1)
			cout << g[1][c & 1] << '\n';
	}
	init(n);
}

int main()
{
	int t;
	cin >> t;
	while (t--)
		solve();
	return 0;
}
posted @ 2026-10-24 10:25  SigmaToT  阅读(16)  评论(0)    收藏  举报