[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;
}

浙公网安备 33010602011771号