Codeforces 633F - The Chocolate Spree(树形 dp)
看来我这个蒟蒻现在也只配刷刷 *2600 左右的题了/dk
这里提供一个奇奇怪怪的大常数做法。
首先还是考虑分析“两条不相交路径”的性质。我们以 \(1\) 号点为根。记 \(subtree(u)\) 为 \(u\) 子树内点的集合,那么对于两条不相交的路径 \(P_1,P_2\),下面两条一定有至少一条成立:
- \(\exist u\) 满足 \(\forall v\in P_1,v\in subtree(u),\forall v\in subtree(u),v\notin P_2\)
- \(\exist u\) 满足 \(\forall v\in P_2,v\in subtree(u),\forall v\in subtree(u),v\notin P_1\)
用人话说就是两条路径中一定存在一条路径满足另一条路径中任何一个点都不在其 LCA 的子树中。
我们不妨枚举这条路径的 LCA \(u\),按照树的直径的套路我们求出 \(f_u\) 表示以 \(u\) 为链顶的链中,权值最大的链的权值之和;\(g_u\) 表示以 \(u\) 为链顶的链中权值第二大的,并且链向下延伸到的儿子与 \(f_u\) 表示的链向下延伸到的儿子不同的链的权值之和(可能讲的不是太清楚,不过学过树的直径的应该都知道是什么意思罢)。那么显然以 \(u\) 为 LCA 的链权值之和的最大值就是 \(f_u+g_u-a_u\)。
故现在我们的任务就是求出另一条链长度的最大值,也就是抠掉 \(u\) 的子树后剩余部分的直径长度,这个东西怎么求呢?
我们考虑将剩余部分以 \(fa_u\) 为根,其中 \(fa_i\) 为 \(i\) 在原树中的父亲。
还是套路地枚举第二条路径在剩余子树部分中的 LCA \(v\),记 \(f'_u\) 为在新的树中以 \(u\) 为链顶的链中,权值最大的链的权值之和;\(g'_u\) 表示以 \(u\) 为链顶的链中权值第二大的,并且链向下延伸到的儿子与 \(g'_u\) 表示的链向下延伸到的儿子不同的链的权值之和。那么这部分对答案的贡献应当为 \(f_u+g_u-a_u+\max\{f'_v+g'_v-a_v\}\)。
但是显然我们每次枚举 \(u\) 都重新求一遍 \(f'_u,g'_u\) 是不现实的,我们不妨来探究一下 \(f'_u,g'_u\) 究竟与 \(f_u,g_u\) 有什么关系。
比方说有如下图所示的树:

假设我们枚举到了点 \(U\),原树的根为 \(R\)。我们抠掉 \(U\) 的子树部分之后以 \(F\) 为根,剩余部分长这样:

考虑将剩余的子树中的节点 \(v\) 分为两类:
- \(v\) 不在 \(u\) 到根节点的路径上,譬如图中的 \(A\) 点,很容易注意到的一点是新树中 \(A\) 子树内的点集与原树中 \(A\) 子树内的点集相同,故对于这样的点 \(v\) 一定有 \(f'_v=f_v,g'_v=g_v\)。而显然点 \(v\) 满足该条件当且仅当 \(v\) 与 \(u\) 不存在祖先关系。如果我们记 \(h_u=f_u+g_u-a_u\),那么这部分的贡献应为 \(h_u+h_v\)。故所有这样的点对 \((u,v)\) 所能产生的最大贡献就是 \(\max\limits_{u,v\ \text{不存在祖先关系}}h_u+h_v\),考虑枚举 \(w=\text{LCA}(u,v)\),记 \(mx_u=\max\limits_{v\in subtree(u)}h_v\),那么以 \(w\) 为 LCA 的 \(u,v\) 产生的贡献的最大值就是 \(\max\limits_{u,v\in son(w),u\ne v}mx_u+mx_v\),这个显然可以一遍 DFS 搞定,在 DFS 过程中记录最小值和次小值即可。
- \(v\) 在 \(u\) 到根节点的路径上,譬如图中的 \(B\) 点,对于这样点 \(v\),也有 \(u\) 在新子树内的点集就是 \(\{1,2,\dots,n\}\) 扣掉原树内 \(v\) 在 \(u\) 方向的儿子的子树内的点集。我们记 \(of_v\) 为扣掉 \(v\) 的子树内的后,以 \(fa_v\) 为根后,以 \(v\) 为链顶的链的权值的最大值,\(og_v\) 仿照前文也有类似的定义。那么 \(f'_v=of_s,g'_v=og_s\),其中 \(s\) 为 \(v\) 在 \(u\) 方向的儿子。考虑一遍 DFS 用换根 dp 的套路求出 \(of_u,og_u\),那么这部分贡献的最大值就是 \(f_u+g_u-a_u+\max\limits_{v\ \text{为}\ u\ \text{的祖先},v\ne 1}\{of_v+og_v-a_v\}\),这个又可以一遍 DFS 求出。
时间复杂度 \(\mathcal O(n)\),总共需四遍 DFS,第一遍 DFS 求出 \(f_i,g_i\),第二遍 DFS 用换根 dp 的套路求出 \(of_i,og_i\),第三遍 DFS 求出 \(\max\limits_{u,v\ \text{不存在祖先关系}}h_u+h_v\),第四遍 DFS 求出 \(\max f_u+g_u-a_u+\max\limits_{v\ \text{为}\ u\ \text{的祖先},v\ne 1}\{of_v+og_v-a_v\}\)。常数较大,跑得最慢的一个点跑了 171ms。
代码如下(代码中 dfs1,dfs2,dfs3,dfs4 分别对应上文中提到的四遍 DFS):
#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define fill0(a) memset(a,0,sizeof(a))
#define fill1(a) memset(a,-1,sizeof(a))
#define fillbig(a) memset(a,63,sizeof(a))
#define pb push_back
#define ppb pop_back
#define mp make_pair
template<typename T1,typename T2> void chkmin(T1 &x,T2 y){if(x>y) x=y;}
template<typename T1,typename T2> void chkmax(T1 &x,T2 y){if(x<y) x=y;}
typedef pair<int,int> pii;
typedef long long ll;
typedef unsigned int u32;
typedef unsigned long long u64;
namespace fastio{
#define FILE_SIZE 1<<23
char rbuf[FILE_SIZE],*p1=rbuf,*p2=rbuf,wbuf[FILE_SIZE],*p3=wbuf;
inline char getc(){return p1==p2&&(p2=(p1=rbuf)+fread(rbuf,1,FILE_SIZE,stdin),p1==p2)?-1:*p1++;}
inline void putc(char x){(*p3++=x);}
template<typename T> void read(T &x){
x=0;char c=getchar();T neg=0;
while(!isdigit(c)) neg|=!(c^'-'),c=getchar();
while(isdigit(c)) x=(x<<3)+(x<<1)+(c^48),c=getchar();
if(neg) x=(~x)+1;
}
template<typename T> void recursive_print(T x){if(!x) return;recursive_print(x/10);putc(x%10^48);}
template<typename T> void print(T x){if(!x) putc('0');if(x<0) putc('-'),x=~x+1;recursive_print(x);}
void print_final(){fwrite(wbuf,1,p3-wbuf,stdout);}
}
const int MAXN=1e5;
int n,a[MAXN+5];
vector<int> g[MAXN+5];
vector<pair<ll,ll> > pre[MAXN+5];
vector<pair<ll,ll> > suf[MAXN+5];
pair<ll,ll> out[MAXN+5],dp[MAXN+5];
pair<ll,ll> merge(pair<ll,ll> x,ll y){
ll a[3]={0};a[0]=x.fi;a[1]=x.se;a[2]=y;
sort(a,a+3);reverse(a,a+3);return mp(a[0],a[1]);
}
void dfs1(int x=1,int f=0){
for(int i=0;i<g[x].size();i++){
int y=g[x][i];if(y==f) continue;
dfs1(y,x);dp[x]=merge(dp[x],dp[y].fi+a[y]);
}
}
void dfs2(int x=1,int f=0){
pre[x].resize(g[x].size()+2);
suf[x].resize(g[x].size()+2);
for(int i=0;i<g[x].size();i++){
int y=g[x][i];
if(y==f) pre[x][i+1]=merge(pre[x][i],out[x].fi+a[f]);
else pre[x][i+1]=merge(pre[x][i],dp[y].fi+a[y]);
}
for(int i=(int)g[x].size()-1;~i;i--){
int y=g[x][i];
if(y==f) suf[x][i+1]=merge(suf[x][i+2],out[x].fi+a[f]);
else suf[x][i+1]=merge(suf[x][i+2],dp[y].fi+a[y]);
}
for(int i=0;i<g[x].size();i++){
int y=g[x][i];if(y==f) continue;
ll a[4]={0};
a[0]=pre[x][i].fi;a[1]=pre[x][i].se;
a[2]=suf[x][i+2].fi;a[3]=suf[x][i+2].se;
sort(a,a+4);reverse(a,a+4);out[y]=mp(a[0],a[1]);
dfs2(y,x);
}
}
ll mx[MAXN+5],ans=0;
void dfs3(int x=1,int f=0){
mx[x]=dp[x].fi+dp[x].se+a[x];
ll mx1=0,mx2=0;
for(int i=0;i<g[x].size();i++){
int y=g[x][i];if(y==f) continue;
dfs3(y,x);chkmax(mx[x],mx[y]);
if(mx[y]>mx1) mx2=mx1,mx1=mx[y];
else if(mx[y]>mx2) mx2=mx[y];
}
if(mx2) chkmax(ans,mx1+mx2);
}
ll mxx[MAXN+5];
void dfs4(int x=1,int f=0){
if(x!=1) chkmax(ans,dp[x].fi+dp[x].se+a[x]+mxx[x]);
for(int i=0;i<g[x].size();i++){
int y=g[x][i];if(y==f) continue;
mxx[y]=max(mxx[x],out[y].fi+out[y].se+a[x]);dfs4(y,x);
}
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1,u,v;i<n;i++) scanf("%d%d",&u,&v),g[u].pb(v),g[v].pb(u);
dfs1();dfs2();dfs3();dfs4();printf("%lld\n",ans);
return 0;
}
/*1
9
1 2 3 4 5 6 7 8 9
1 2
1 3
1 4
1 5
1 6
1 7
1 8
1 9
*/
/*2
2
20 10
1 2
*/
/*
5
1 5 4 2 3
1 2
2 3
2 4
1 5
*/
Codeforces 633F - The Chocolate Spree(树形 dp)的更多相关文章
- Codeforces 633F The Chocolate Spree 树形dp
The Chocolate Spree 对拍拍了半天才知道哪里写错了.. dp[ i ][ j ][ k ]表示在 i 这棵子树中有 j 条链, 是否有链延伸上来. #include<bits/ ...
- codeforces 633F The Chocolate Spree (树形dp)
题目链接:http://codeforces.com/problemset/problem/633/F 题解:看起来很像是树形dp其实就是单纯的树上递归,就是挺难想到的. 显然要求最优解肯定是取最大的 ...
- CF 633 F. The Chocolate Spree 树形dp
题目链接 CF 633 F. The Chocolate Spree 题解 维护子数答案 子数直径 子数最远点 单子数最长直径 (最长的 最远点+一条链) 讨论转移 代码 #include<ve ...
- cf633F. The Chocolate Spree(树形dp)
题意 题目链接 \(n\)个节点的树,点有点权,找出互不相交的两条链,使得权值和最大 Sol 这辈子也不会写树形dp的 也就是有几种情况,可以讨论一下.. 下文的"最大值"指的是& ...
- Codeforces 633F 树的直径/树形DP
题意:有两个小孩玩游戏,每个小孩可以选择一个起始点,并且下一个选择的点必须和自己选择的上一个点相邻,问两个选的点权和的最大值是多少? 思路:首先这个问题可以转化为求树上两不相交路径的点权和的最大值,对 ...
- codeforces 161D Distance in Tree 树形dp
题目链接: http://codeforces.com/contest/161/problem/D D. Distance in Tree time limit per test 3 secondsm ...
- codeforces 337D Book of Evil (树形dp)
题目链接:http://codeforces.com/problemset/problem/337/D 参考博客:http://www.cnblogs.com/chanme/p/3265913 题目大 ...
- Codeforces 1276D - Tree Elimination(树形 dp)
Codeforces 题面传送门 & 洛谷题面传送门 繁琐的简单树形 dp(大雾),要是现场肯定弃了去做 F 题 做了我一中午,写篇题解纪念下. 提供一种不太一样的思路. 首先碰到这样的题肯定 ...
- Codeforces 543D Road Improvement(树形DP + 乘法逆元)
题目大概说给一棵树,树的边一开始都是损坏的,要修复一些边,修复完后要满足各个点到根的路径上最多只有一条坏的边,现在以各个点为根分别求出修复边的方案数,其结果模1000000007. 不难联想到这题和H ...
随机推荐
- Flink Yarn的2种任务提交方式
Flink Yarn的2种任务提交方式 Pre-Job模式介绍 每次使用flink run运行任务的时候,Yarn都会重新申请Flink集群资源(JobManager和TaskManager),任务执 ...
- Github 29K Star的开源对象存储方案——Minio入门宝典
对象存储不是什么新技术了,但是从来都没有被替代掉.为什么?在这个大数据发展迅速地时代,数据已经不单单是简单的文本数据了,每天有大量的图片,视频数据产生,在短视频火爆的今天,这个数量还在增加.有数据表明 ...
- Asp.CAore往Vue前端传application/octet-stream类型文件流
题外话:当传递文件流时要确定文件流的类型,但也有例外就是application/octet-stream类型,主要是只用来下载的类型,这个类型简单理解意思就是通用类型类似 var .object.ar ...
- oracle物化视图创建及删除
--删除物化表的日志表 DROP MATERIALIZED VIEW LOG ON 表名; --为将要创建物化视图的表添加带主键的日志表 CREATE MATERIALIZED VIEW LOG ON ...
- 跟着老猫来搞GO,集跬步而致千里
上次博客中,老猫已经和大家同步了如何搭建相关的GO语言的开发环境,相信在车上的小伙伴应该都已经搞定了环境了.那么本篇开始,我们就来熟悉GO语言的基础语法.本篇搞定之后,其实期待大家可以和老猫一样,能够 ...
- K8s 离线集群部署(二进制包无dashboard)
https://www.cnblogs.com/cocowool/p/install_k8s_offline.html https://www.jianshu.com/p/073577bdec98 h ...
- SkyWalking部署及.Net Core简单使用
SkyWalking官方网站非常详细,以下只是本人学习过程的整理 一.SkyWalking简介 1.概念 SkyWalking是分布式系统的应用程序性能监视工具,专为微服务.云原生架构而设计 SkyW ...
- 2021 ICPC 江西省赛总结
比赛链接:https://ac.nowcoder.com/acm/contest/21592 大三的第一场正式赛,之前的几次网络赛和选拔赛都有雄哥坐镇,所以并没有觉得很慌毕竟校排只取每个学校成 ...
- 一文搞懂js中的typeof用法
基础 typeof 运算符是 javascript 的基础知识点,尽管它存在一定的局限性(见下文),但在前端js的实际编码过程中,仍然是使用比较多的类型判断方式. 因此,掌握该运算符的特点,对于写出好 ...
- SpringBoot目录文件结构总结(5)
1.目录 src/main/java :存放java代码 src/main/resources static:存放静态文件,比如css.js.image(访问方式 http://localhost:8 ...