G-升!龙!_牛客周赛 Round 88
G-升!龙!_牛客周赛 Round 88
树上 d p dp dp,树上前缀和,树的链剖
题目大意:
给一颗有 n n n 节点的树,每个节点的权值为 a i a_i ai 。
定义:从根节点 1 1 1 到 叶子节点的 简单路径 的 权值之和为 该路径的美丽值。
树的价值为 路径美丽值的最大值。
给定 q q q 次操作,每次操作将 x x x 节点断开,接到 y y y 节点上,每次操作计算处树的价值,操作后将树恢复原样
思路:
- 首先,在不操作的时候先处理出 所有树的简单路径 的权值之和
可以用树上 前缀和 实现:
vector<int> pre[N];//从根节点1到每个节点的权值之和
dfs(1);
//从根节点开始,每次到一个点就用该点的父节点更新该点到根节点的权值之和
//从上往下dfs更新
void dfs(int u) {
pre[u]=pre[fa[u]]+a[u];
for(auto x:g[u]) {
dfs(x);
}
}
- 那么在处理所有简单路径权值之和的时候,因为要求最大的简单路径之和,需要保留一下叶子节点的树上前缀和
将叶子节点存储到 l e a f leaf leaf 数组中,并记录它是第几个被遍历到的叶子节点,存入 d f n dfn dfn 数组中
vector<int> leaf,dfn(n+1);
dfs(1);
void dfs(int u) {
if(g[u].size()==0) {
//叶子节点
dfn[u]=leaf.size()+1;//标记u节点为第leaf.size()+1出现的
leaf.push_back(u);//将u节点这个叶子节点存入leaf
return ;
}
for(auto x:g[u]){
dfs(x);
}
}
- 记录完 l e a f leaf leaf 节点的路径之和后,就可以考虑在不操作的时候如何 ( O 1 ) (O1) (O1) 解决最大的路径查询
预处理处叶子节点 l e a f leaf leaf 的前缀最大值数组 p r e _ m x pre\_mx pre_mx ,
如果考虑操作,那么就应该考虑其中有部分连续区间要被带走,那么应该处理出叶子节点 l e a f leaf leaf 的前缀最大值数组 p r e _ m x pre\_mx pre_mx 和 后缀最大值数组 s u f _ m x suf\_mx suf_mx
vector<int> pre_mx(n+10,0),suf_mx(n+10,0);
//将leaf整体后移一位更好处理前缀和
for(int i=1;i<leaf.size();i++){
pre_mx[i]=max(pre_mx[i-1],pre[leaf[i]]);
}
for(int i=leaf.size()-1;i>=1;i--){
suf_mx[i]=max(suf_mx[i+1],pre[leaf[i]]);
}
- 考虑将 x x x 节点断开,损失的叶子节点的区间,可以考虑记录每个点能到达的最左边叶子 L i L_i Li 和能到达的最右边叶子 R i R_i Ri 可以考虑递归处理
vector<int> L(n+1),R(n+1);
dfs(1);
//先dfs到叶子节点,再由下往上更新
void dfs(int u) {
if(g[u].size()==0) {
L[u]=g[u]=u;
return ;
}
for(auto x:g[u]) {
dfs(x);
}
//处理完叶子节点的L,R后,往上递归处理父节点的
L[u]=L[g[u].front()];
R[u]=R[g[u].back()];
}
将 x x x 断开时, L [ x ] L[x] L[x] 记录的即为断开区间的左端点, R [ x ] R[x] R[x] 记录的为右端点
d f n [ L [ x ] ] dfn[L[x]] dfn[L[x]] 即为该点在 l e a f leaf leaf 数组中的下标
那么可以求出具体哪一段被断掉了
ans=max(ans,pre_mx[dfn[L[x]]]-1)//前面区间的最大值
ans=max(ans,suf_mx[dfn[R[x]]]+1)//后面区间的最大值
断掉这个区间之后,如果 x x x 的父节点为叶节点,那么需要考虑 p r e [ f a [ x ] ] pre[fa[x]] pre[fa[x]] ,如果不是叶节点,那么这个值不优,所以和 a n s ans ans 取 m a x max max 无影响
ans=max(ans,pre[fa[x]));
- 最后,需要考虑将 x x x 接到 y y y 节点上产生的新最大路径
考虑这个新最大路径应该是 p r e [ y ] pre[y] pre[y] + 以 x x x 到 x x x 的叶节点的最大值, x x x 到 x x x 的叶节点的最大值 可以考虑树上 d p dp dp 维护。
vector<int> son_mx(n+1,0);
dfs(1);
//先递归到叶子节点,再由下往上更新
void dfs(int u){
if(g[u].size()==0){
son_mx[u]=a[u];
return ;
}
for(auto x:g[u]){
dfs(x);
son_mx[u]=max(son_mx[u],son_mx[x]+a[u]);
}
}
处理完新最大路径,那么这道题就结束了
ans=max(ans,pre[y]+son_mx[x]);
代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define fi first
#define se second
#define PII pair<int,int>
#define lowbit(x) x&-x
#define ALL(x) x.begin(),x.end()
const int mod = 1e9 + 7;
const int N = 2e5 + 10;
int n,q;
vector<int> a(N),fa(N);
vector<int> g[N];
vector<int> pre(N),pre_mx(N),suf_mx(N);
vector<int> L(N),R(N),leaf(1,0),dfn(N);
vector<int> son_mx(N);
void dfs(int u) {
pre[u]=pre[fa[u]]+a[u];
if(g[u].size()==0){
L[u]=R[u]=u;
dfn[u]=leaf.size();
leaf.push_back(u);
son_mx[u]=a[u];
return ;
}
for(auto x:g[u]){
dfs(x);
son_mx[u]=max(son_mx[u],son_mx[x]+a[u]);
}
L[u]=L[g[u].front()];
R[u]=R[g[u].back()];
}
void solve() {
int n,q;cin>>n>>q;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=2;i<=n;i++){
cin>>fa[i];
g[fa[i]].push_back(i);
}
dfs(1);
for(int i=1;i<leaf.size();i++){
pre_mx[i]=max(pre_mx[i-1],pre[leaf[i]]);
}
for(int i=leaf.size()-1;i>=1;i--){
suf_mx[i]=max(suf_mx[i+1],pre[leaf[i]]);
}
while(q--){
int x,y;cin>>x>>y;
int ans=max(pre_mx[dfn[L[x]]-1],suf_mx[dfn[R[x]]+1]);
ans=max(ans,pre[fa[x]]);
ans=max(ans,pre[y]+son_mx[x]);
cout<<ans<<'\n';
}
}
signed main() {
std::ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int T = 1;
// cin >> T;
while (T--) {
solve();
}
return 0;
}