题解:P9352 [JOI 2023 Final] 训猫 / Cat Exercise
· 阅读需 5 分钟
题目链接
题意简述
一棵树上有 个权值不同的点,一开始有一只猫位于树上权值最大的点上。
每次可以选择在一个点上放置障碍,如果猫所在的点被放置障碍,则它会沿简单路径移动到能到达的点中权值最大的一个点上。
求能达成的猫的最大移动距离和。
解题思路
给出一个不太一样的思路。
考虑到因为给出的是一棵树,因此一旦离开某个点,就不可能到达与该点相连的其他点。
基于这一性质,实际上每次移动都会将猫的移动范围限制在上一个点的某棵子树内。
考虑进行搜索,传递状态为 ,搜索以 为根的子树内,从该子树内权值最大的点开始能达到的最长移动距离和。
对于权值最大点的子树,这是好做的,我们只需要将结果取最大值即可。
设 表示以点 为根的子树内权值最大的点,于是我们就要考虑如何处理在以 为根的子树内而在以 为根的子树外的点。
直接做显然不好做,但是因为以 为根的子树的内容不会影响到别的子树的结果。
所以我们可以在计算完以 为根的子树之后直接将这一子树删去,然后再对剩余部分直接计算贡献,可以发现实际上就是在计算在以 为根的子树内而在以 为根的子树外的点的贡献。
只需要用线段树维护 dfn 进行删点(将权值更改为 )和查询区间最大值操作即可。
复杂度为
参考代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll N = 200005;
ll n , h[N] , fa[N] , dfn[N] , pos[N] , dfncnt = 0;
ll top[N] , dep[N] , siz[N] , son[N];
bool del[N];
vector <ll> g[N];
struct seg {ll l , r , mx , tag;} s[N << 2];
void dfs(ll u , ll f)
{
fa[u] = f; siz[u] = 1; dep[u] = dep[f] + 1;
pos[dfn[u] = ++dfncnt] = u;
for(auto v : g[u]) if(v != f)
dfs(v , u) , siz[u] += siz[v],
son[u] = (siz[son[u]] < siz[v] ? v : son[u]);
}
void dfs2(ll u)
{
if(!son[u]) return;
top[son[u]] = top[u];
dfs2(son[u]);
for(auto v : g[u]) if(v != fa[u] && v != son[u])
top[v] = v , dfs2(v);
}
ll lca(ll u , ll v)
{
while(top[u] != top[v])
{
if(dep[top[u]] < dep[top[v]]) swap(u , v);
u = fa[top[u]];
}
return dep[u] < dep[v] ? u : v;
}
ll dis(ll u , ll v) {return dep[u] + dep[v] - 2 * dep[lca(u , v)];}
void build(ll id , ll l , ll r)
{
s[id].l = l; s[id].r = r;
if(l == r) {s[id].mx = pos[l]; return;}
ll mid = (l + r) >> 1;
build(id << 1 , l , mid); build(id << 1 | 1 , mid + 1 , r);
s[id].mx = h[s[id << 1].mx] > h[s[id << 1 | 1].mx] ? s[id << 1].mx : s[id << 1 | 1].mx;
}
void pushdown(ll id)
{
if(s[id].tag)
{
s[id << 1].tag = 1; s[id << 1].mx = 0;
s[id << 1 | 1].tag = 1; s[id << 1 | 1].mx = 0;
s[id].tag = 0;
}
}
void update(ll id , ll l , ll r)
{
if(s[id].l > r || s[id].r < l || s[id].tag) return;
if(s[id].l >= l && s[id].r <= r) {s[id].tag = 1; s[id].mx = 0; return;}
pushdown(id);
update(id << 1 , l , r); update(id << 1 | 1 , l , r);
s[id].mx = h[s[id << 1].mx] > h[s[id << 1 | 1].mx] ? s[id << 1].mx : s[id << 1 | 1].mx;
}
ll query(ll id , ll l , ll r)
{
if(s[id].l > r || s[id].r < l || s[id].tag) return 0;
if(s[id].l >= l && s[id].r <= r) return s[id].mx;
ll la = query(id << 1 , l , r) , ra = query(id << 1 | 1 , l , r);
return h[la] > h[ra] ? la : ra;
}
ll find_ans(ll u)
{
ll v = query(1 , dfn[u] , dfn[u] + siz[u] - 1) , res = 0;
if(!v) return 0;
for(auto w : g[v])
if(w != fa[v] && !del[w])
{
ll tmp = dis(v , query(1 , dfn[w] , dfn[w] + siz[w] - 1));
res = max(res , find_ans(w) + tmp);
}
update(1 , dfn[v] , dfn[v] + siz[v] - 1); del[v] = 1;
if(u != v)
{
ll tmp = dis(query(1 , dfn[u] , dfn[u] + siz[u] - 1) , v);
res = max(res , find_ans(u) + tmp);
}
return res;
}
signed main()
{
freopen(".in" , "r" , stdin);
freopen(".out" , "w" , stdout);
cin >> n;
for(ll i = 1 ; i <= n ; i++) cin >> h[i];
for(ll i = 1 , u , v ; i < n ; i++)
cin >> u >> v , g[u].push_back(v) , g[v].push_back(u);
dfs(1 , 0); top[1] = 1; dfs2(1); build(1 , 1 , n);
cout << find_ans(1) << '\n';
return 0;
}
