分析
首先考虑朴素算法。
可以枚举每个节点作为根,这样的话连接的树的形态就确定了。
应为我们可以知道每个点只可能是一个蓝边的中点,不难想到设一个DP状态表示节点是否为蓝边的中点,设
d
p
[
i
]
[
0
]
dp[i][0]
dp[i][0] 表示不是蓝边的中点,
d
p
[
i
]
[
1
]
dp[i][1]
dp[i][1] 表示是蓝边的中点。因为每个蓝边的线段长都是两个线段,所以说对于一个是蓝边中点的节点,那么会想到是
s
o
n
−
>
x
−
>
f
a
t
h
e
r
son->x->father
son−>x−>father ,或者是
s
o
n
−
>
x
−
>
s
o
n
son->x->son
son−>x−>son 。但是,仔细地推一下,会发现只可能是
s
o
n
−
>
x
−
>
f
a
t
h
e
r
son->x->father
son−>x−>father ,一种连法。
如果这么连,那么我们可以发现不符合 I n s e r t Insert Insert 新加节点的法则。于是只能 s o n − > x − > f a t h e r son->x->father son−>x−>father 这么连了。
考虑出来这个性质,我们就可以DP了。考虑子节点对于当前节点的贡献,如果子节点是蓝边的中点,那么这个节点一定不是蓝边的中点,贡献就是子节点的权值加上蓝边的权值。
d
p
[
x
]
[
0
]
=
∑
m
a
x
(
d
p
[
s
o
n
]
[
0
]
,
d
p
[
s
o
n
]
[
1
]
+
w
[
x
]
[
s
o
n
]
)
dp[x][0] =\sum max(dp[son][0],dp[son][1] + w[x][son])
dp[x][0]=∑max(dp[son][0],dp[son][1]+w[x][son])
如果当前节点是蓝边的中点,那么子节点就不是蓝边的中点,所以子节点对他的贡献就是就是子节点的权值加上蓝边的权值。
d p [ x ] [ 1 ] = d p [ x ] [ 0 ] + m a x ( d p [ s o n ] [ 0 ] + w [ x ] [ s o n ] − m a x ( d p [ s o n ] [ 0 ] , d p [ s o n ] [ 1 ] + w [ x ] [ s o n ] ) ) dp[x][1] = dp[x][0] + max(dp[son][0] + w[x][son] - max(dp[son][0],dp[son][1] + w[x][son])) dp[x][1]=dp[x][0]+max(dp[son][0]+w[x][son]−max(dp[son][0],dp[son][1]+w[x][son]))
这就是暴力的解法,时间复杂度 O ( n 2 ) O(n^2) O(n2) 。
换根
暴力的解法终究是会T的,想到删去枚举根的操作。
对于每个节点,就是儿子节点翻身做父亲了,他的权值就是父亲节点和自己的儿子节点。做法还是比较套路,因为考虑到可能会破坏掉最大值,于是维护一下次大值就行了。因为当前枚举到的儿子变成了父亲,就不能得到儿子的值。很直接,删掉就是了。这样考虑时并没有考虑到设个节点作为根后父亲节点对它的贡献,与第一次DP时的DP式相同。根需要特判,因为他并没有父亲节点,最后对于每个节点作为根的答案就是儿子节点子树的权值和当前节点的DP值之和。
code
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#define INF 0x7f7f7f7f
using namespace std;
const int MAXN = 4e5 + 5;
int n,head[MAXN],tot,dp[MAXN][3],ans,rdp[MAXN][3];
vector<int> Vec[MAXN][3];
// dp[x][0] x不作为蓝线的中点
// dp[x][1] x作为蓝线的中点
// Insert : son -> x -> fa
// Append : x -> fa OR son-> x
// Vec[x][0][k] x不作为蓝线的中点且不包含k号子节点
// Vec[x][1][k] x作为蓝线的中点且不包含k号子节点
// Vec[x][2][k] x到k号节点链式前向星的编号
// rdp[x][0] 当前计算时值
struct node{
int To,Next,val;
}edge[MAXN];
void Add_Edge(int x,int y,int z) {
edge[++tot].Next = head[x];
edge[tot].To = y;
edge[tot].val = z;
head[x] = tot;
}
void First_Tree_Dp(int x,int fa) {
int maxx = -INF,rmaxx = -INF;
for(int i = head[x];i;i = edge[i].Next) {
int To = edge[i].To;
if(To == fa) continue;
First_Tree_Dp(To,x);
dp[x][0] += max(dp[To][0],dp[To][1] + edge[i].val);
if(dp[To][0] + edge[i].val - max(dp[To][0],dp[To][1] + edge[i].val) > maxx)//维护最大值和最小值
rmaxx = maxx,maxx = dp[To][0] + edge[i].val - max(dp[To][0],dp[To][1] + edge[i].val);
else if(dp[To][0] + edge[i].val - max(dp[To][0],dp[To][1] + edge[i].val) > rmaxx)
rmaxx = dp[To][0] + edge[i].val - max(dp[To][0],dp[To][1] + edge[i].val);
dp[x][1] = max(dp[x][1],dp[To][0] + edge[i].val - max(dp[To][0],dp[To][1] + edge[i].val));
}
dp[x][1] += dp[x][0];
for(int i = head[x];i;i = edge[i].Next) {
int To = edge[i].To;
if(To == fa) continue;
Vec[x][0].push_back(dp[x][0] - max(dp[To][0],dp[To][1] + edge[i].val));//对于删去每个子节点的权值
if(dp[To][0] + edge[i].val - max(dp[To][0],dp[To][1] + edge[i].val) == maxx)//破坏了最大值
Vec[x][1].push_back(rmaxx);//加入次大值
else Vec[x][1].push_back(maxx);
Vec[x][2].push_back(i);//存储链式前向星的编号以便第二次DP权值和子节点
}
}
void Second_Tree_Dp(int x,int fa,int pre) {
for(int i = 0;i < Vec[x][0].size();i ++) {
rdp[x][0] = Vec[x][0][i];
int fpre = rdp[x][0];
rdp[x][1] = Vec[x][1][i];
int To = edge[Vec[x][2][i]].To;
if(fa) {//考虑父亲对当前节点的贡献
rdp[x][0] += max(rdp[fa][0],rdp[fa][1] + pre);
rdp[x][1] = rdp[x][0] + max(rdp[x][1],rdp[fa][0] + pre - max(rdp[fa][0],rdp[fa][1] + pre));
} else rdp[x][1] += fpre;//根没有父亲,所以需要加上本该父亲覆盖的权值
- ans = max(ans,dp[To][0] + max(rdp[x][0],rdp[x][1] + edge[Vec[x][2][i]].val));//答案就是儿子节点与当前节点去除儿子的最大权值之和
Second_Tree_Dp(To,x,edge[Vec[x][2][i]].val);
}
}
void Init() {
for(int i = 1;i <= n;i ++)
dp[i][0] = 0,dp[i][1] = -0x3f3f3f3f;
}
int main() {
scanf("%d",&n);
for(int i = 1;i < n;i ++) {
int a,b,c;
scanf("%d %d %d",&a,&b,&c);
Add_Edge(a,b,c);
Add_Edge(b,a,c);
}
Init();
First_Tree_Dp(1,0);
Second_Tree_Dp(1,0,0);
printf("%d",ans);
return 0;
}