题解 [APIO2014]连珠线
题目大意
在达芬奇时代,有一个流行的儿童游戏称为连珠线。当然,这个游戏是关于珠子和线的。线是红色或蓝色的,珠子被编号为 \(1\) 到 \(n\)。这个游戏从一个珠子开始,每次会用如下方式添加一个新的珠子:
Append(w, v):一个新的珠子 \(w\) 和一个已经添加的珠子 \(v\) 用红线连接起来。
Insert(w, u, v):一个新的珠子 \(w\) 插入到用红线连起来的两个珠子 \(u,v\) 之间。具体过程是删去 \(u,v\) 之间红线,分别用蓝线连接 \(u,w\) 和 \(w,v\)。
每条线都有一个长度。游戏结束后,你的最终得分为蓝线长度之和。
给你连珠线游戏结束后的游戏局面,只告诉了你珠子和链的连接方式以及每条线的长度,没有告诉你每条线分别是什么颜色。
你需要写一个程序来找出最大可能得分。即,在所有以给出的最终局面结束的连珠线游戏中找出那个得分最大的,然后输出最大可能得分。
\(n\le 10^6\)
思路
首先我们通过思考发现这样一个结论:
当树的形态固定的时候,一个点作为中间点的情况当且仅当它的某个儿子连向他,他连向他爸爸
然后我们可以设 \(f_{u,0/1}\) 表示以 \(u\) 为根的子树 \(u\) 这个点是否是中间点的最大贡献。然后我们可以得到转移式:
\[f_{u,0}\to \sum_{v\in son_u} \max\{f_{v,1}+w,f_{u,0}\}
\]
\[f_{u,1}\to \max_{v\in son_u} \{f_{u,0}-\max\{f_{v,1}+w,f_{v,0}\}+f_{v,0}+w\}
\]
然后答案就是 \(f_{root,0}\)。枚举每个根做一遍 \(\text{dp}\) 可以获得 \(\Theta(n^2) \& 40\) 的好成绩。
然后你发现我们这个东西其实是可以换根的,我们只需要维护一个前缀最大值、后缀最大值即可。具体见代码。
时间复杂度 \(\Theta(n)\)。
\(\texttt{Code}\)
#include <bits/stdc++.h>
using namespace std;
#define Int register int
#define INF 0x3f3f3f3f
#define MAXN 200005
template <typename T> inline void read (T &t){t = 0;char c = getchar();int f = 1;while (c < '0' || c > '9'){if (c == '-') f = -f;c = getchar();}while (c >= '0' && c <= '9'){t = (t << 3) + (t << 1) + c - '0';c = getchar();} t *= f;}
template <typename T,typename ... Args> inline void read (T &t,Args&... args){read (t);read (args...);}
template <typename T> inline void write (T x){if (x < 0){x = -x;putchar ('-');}if (x > 9) write (x / 10);putchar (x % 10 + '0');}
int toop = 1,to[MAXN << 1],wei[MAXN << 1],nxt[MAXN << 1],head[MAXN];
void Add_Edge (int u,int v,int w){
to[++ toop] = v,wei[toop] = w,nxt[toop] = head[u],head[u] = toop;
to[++ toop] = u,wei[toop] = w,nxt[toop] = head[v],head[v] = toop;
}
int n,ans,f[MAXN][2];
vector <int> son[MAXN],len[MAXN];
vector <int> pre[MAXN],suf[MAXN];
void dfs1 (int u,int fa){
f[u][0] = 0,f[u][1] = -INF;
for (Int i = head[u];i;i = nxt[i]) if (to[i] ^ fa) son[u].push_back (to[i]),len[u].push_back (wei[i]);
for (Int i = 0;i < son[u].size();++ i){
int v = son[u][i],w = len[u][i];
dfs1 (v,u),f[u][0] += max (f[v][0],f[v][1] + w);
pre[u].push_back (-max (f[v][0],f[v][1] + w) + f[v][0] + w);
suf[u].push_back (-max (f[v][0],f[v][1] + w) + f[v][0] + w);
}
for (Int i = 0;i < son[u].size();++ i) f[u][1] = max (f[u][1],f[u][0] + pre[u][i]);
for (Int i = 1;i < son[u].size();++ i) pre[u][i] = max (pre[u][i],pre[u][i - 1]);
for (Int i = son[u].size() - 2;i >= 0;-- i) suf[u][i] = max (suf[u][i],suf[u][i + 1]);
}
void dfs2 (int u,int fa,int ff0,int ff1,int ffl){
f[u][0] += max (ff0,ff1 + ffl),f[u][1] += max (ff0,ff1 + ffl);
f[u][1] = max (f[u][1],f[u][0] - max (ff0,ff1 + ffl) + ff0 + ffl);
ans = max (ans,f[u][0]);//以u为根的时候显然不能作为中点
for (Int i = 0;i < son[u].size();++ i){
int v = son[u][i],w = len[u][i];
int newf0 = f[u][0] - max (f[v][0],f[v][1] + w);
int delta = -max (ff0,ff1 + ffl) + ff0 + ffl;
if (i != 0) delta = max (delta,pre[u][i - 1]);
if (i < son[u].size() - 1) delta = max (delta,suf[u][i + 1]);
dfs2 (v,u,newf0,newf0 + delta,w);
}
}
signed main(){
read (n);
for (Int i = 2,u,v,w;i <= n;++ i) read (u,v,w),Add_Edge (u,v,w);
dfs1 (1,0),dfs2 (1,0,0,-INF,-INF);
write (ans),putchar ('\n');
return 0;
}
/*
dp[u][0]=\sum_{v\in son_u} max{dp[v][0],dp[v][1]+w}
dp[u][1]=dp[u][0]+max_{v\in son_u} (-max(dp[v][0],dp[v][1]+w)+dp[v][0]+w)
*/