Codeforces 题目传送门 & 洛谷题目传送门

看来我这个蒟蒻现在也只配刷刷 *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\) 分为两类:

  1. \(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 过程中记录最小值和次小值即可。
  2. \(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)的更多相关文章

  1. Codeforces 633F The Chocolate Spree 树形dp

    The Chocolate Spree 对拍拍了半天才知道哪里写错了.. dp[ i ][ j ][ k ]表示在 i 这棵子树中有 j 条链, 是否有链延伸上来. #include<bits/ ...

  2. codeforces 633F The Chocolate Spree (树形dp)

    题目链接:http://codeforces.com/problemset/problem/633/F 题解:看起来很像是树形dp其实就是单纯的树上递归,就是挺难想到的. 显然要求最优解肯定是取最大的 ...

  3. CF 633 F. The Chocolate Spree 树形dp

    题目链接 CF 633 F. The Chocolate Spree 题解 维护子数答案 子数直径 子数最远点 单子数最长直径 (最长的 最远点+一条链) 讨论转移 代码 #include<ve ...

  4. cf633F. The Chocolate Spree(树形dp)

    题意 题目链接 \(n\)个节点的树,点有点权,找出互不相交的两条链,使得权值和最大 Sol 这辈子也不会写树形dp的 也就是有几种情况,可以讨论一下.. 下文的"最大值"指的是& ...

  5. Codeforces 633F 树的直径/树形DP

    题意:有两个小孩玩游戏,每个小孩可以选择一个起始点,并且下一个选择的点必须和自己选择的上一个点相邻,问两个选的点权和的最大值是多少? 思路:首先这个问题可以转化为求树上两不相交路径的点权和的最大值,对 ...

  6. codeforces 161D Distance in Tree 树形dp

    题目链接: http://codeforces.com/contest/161/problem/D D. Distance in Tree time limit per test 3 secondsm ...

  7. codeforces 337D Book of Evil (树形dp)

    题目链接:http://codeforces.com/problemset/problem/337/D 参考博客:http://www.cnblogs.com/chanme/p/3265913 题目大 ...

  8. Codeforces 1276D - Tree Elimination(树形 dp)

    Codeforces 题面传送门 & 洛谷题面传送门 繁琐的简单树形 dp(大雾),要是现场肯定弃了去做 F 题 做了我一中午,写篇题解纪念下. 提供一种不太一样的思路. 首先碰到这样的题肯定 ...

  9. Codeforces 543D Road Improvement(树形DP + 乘法逆元)

    题目大概说给一棵树,树的边一开始都是损坏的,要修复一些边,修复完后要满足各个点到根的路径上最多只有一条坏的边,现在以各个点为根分别求出修复边的方案数,其结果模1000000007. 不难联想到这题和H ...

随机推荐

  1. Unity——EasyTouch摇杆插件使用

    EasyTouch摇杆插件使用 Demo展示 双指缩放在电脑端无法掩饰,竖屏将就看看吧: 插件名叫EasyTouch,有需要给我留言,不想开仓库传了: 创建摇杆点这里: 初始化 On_Joystick ...

  2. 【UE4】虚幻引擎技术直播汇总(含中英文直播)

    B站虚幻引擎官方账号 中文直播 [中文直播]第35期 | 使用GIS在UE中创造真实地球风貌 | Epic 周澄清 [中文直播]第34期 | 包教包会的Epic MegaGrants申请之道 | Ep ...

  3. JDK里常见容器总结

    自己总结.   扩容 线程安全   是否支持null 的key 说明 hashmap 2*length 否   是 1.8以后增加红黑树.提高检索效率 hashtable   是   否 官方不建议使 ...

  4. 使用jave2实现将wav格式的音频转换成mp3格式

    最近需要用到语音合成功能,网上查阅了一番,发现可以使用腾讯云的语音合成API来完成这个功能,但是腾讯云的api返回的是wav格式的音频文件,这个格式的文件有些不通用,因此需要转换成mp3格式的文件. ...

  5. 人人都写过的5个Bug!

    大家好,我是良许. 计算机专业的小伙伴,在学校期间一定学过 C 语言.它是众多高级语言的鼻祖,深入学习这门语言会对计算机原理.操作系统.内存管理等等底层相关的知识会有更深入的了解,所以我在直播的时候, ...

  6. Noip模拟72 2021.10.9

    T1 出了个大阴间题 真就以为他出了个大阴间题就没写,打个暴力就跑了 数据范围显然摆明是状压 设$f[sta][0/1]$表示在已经选择的集合$sta$中,$A$的最大值是$A$还是$A+1$ 然后按 ...

  7. Linux多线程编程实例解析

    Linux系统下的多线程遵循POSIX线程接口,称为 pthread.编写Linux下的多线程程序,需要使用头文件pthread.h,连接时需要使用库libpthread.a.顺便说一下,Linux ...

  8. ArrayList集合底层原理

    目录 ArrayList集合特点及源码分析 ArrayList源码分析 成员变量 构造函数 增加方法 add(E e)方法 add(int index, E element)方法 删除方法 remov ...

  9. 链式A+B 牛客网 程序员面试金典 C++ Python

    链式A+B 牛客网 程序员面试金典 C++ Python 题目描述 有两个用链表表示的整数,每个结点包含一个数位.这些数位是反向存放的,也就是个位排在链表的首部.编写函数对这两个整数求和,并用链表形式 ...

  10. 『学了就忘』Linux基础 — 16、Linux系统与Windows系统的不同

    目录 1.Linux严格区分大小写 2.Linux一切皆文件 3.Linux不靠扩展名区分文件类型 4.Linux中所有的存储设备都必须在挂载之后才能使用 5.Windows下的程序不能直接在Linu ...