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. 3.2 Dependencies of the Projects in the Solution 解决方案中项目间的依赖项

    3.2 Dependencies of the Projects in the Solution 解决方案中项目间的依赖项 The diagram below shows the essential ...

  2. VS Code Remote SSH设置

    本文翻译自:5 Steps: Setup VS Code for Remote Development via SSH from Windows to Linux system 5个步骤:设置VS代码 ...

  3. DDD领域驱动设计-概述-Ⅰ

     如果我看得更远,那是因为我站在巨人的肩膀上.(If I have seen further it is by standing on ye shoulder of Giants.)         ...

  4. 热身训练1 ping ping ping

    点此进入 题意: 一棵树,n+1 个节点,以0号节点为根,给出端点(a,b),节点a到节点b的路径上,至少有一个点是"坏掉的",求"坏掉的点"最少 分析: St ...

  5. clone-graph leetcode C++

    Clone an undirected graph. Each node in the graph contains alabeland a list of itsneighbors. OJ's un ...

  6. 最短路计数(SPFA× Dijkstra√)

    题目描述 给出一个n个顶点m条边的无向无权图,顶点编号为1−n.问从顶点1开始,到其他每个点的最短路有几条. 输入格式 第一行包含2个正整数n,m,为图的顶点数与边数. 接下来M行,每行2个正整数x, ...

  7. Java项目中集成钉钉机器人推送消息提醒

    前言: 项目中有一个需求,当有新订单产生的时候,希望能够及时通知到业务相关人员进行处理,整体考虑了一下,选用了钉钉机器人提醒功能(公司内部主要也是使用钉钉进行通讯). 操作: 主要分为两部分进行处理: ...

  8. Ubuntu 安装 mysql 报错 "update-alternatives: 错误: 候选项路径 /etc/mysql/mysql.cnf 不存在"

    解决方法: sudo cp /etc/mysql/my.cnf /etc/mysql/mysql.cnf 偷梁换柱-! 如果想更新mysql的源方法如下: wget http://dev.mysql. ...

  9. log4j日志集成

    一.介绍  Log4j是Apache的一个开放源代码项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台.文件.GUI组件.甚至是套接口服务 器.NT的事件记录器.UNIX Syslog ...

  10. Obsidian中如何记录自己的灵感?

    在生活中当中你是否会在某个瞬间产生一个想法,但没过多久就想不起来了,正所谓灵感转瞬即逝,那我们不妨在灵感出现的时候顺手将他记录下来.记录的过程要求简单.方便且不会花费我们太多时间,下面我们介绍一下如何 ...