Part0
的暴力
我们看一下这个这一个连珠线的产生过程。
确定了一个根后,我们定义折链为LCA不为这两个点的链。就像下图这样。
我们定义中心点为一对蓝线产生时加入的点,每个点只能为一对蓝线产生时加入的点。
如果我们确定一个根,一个个在原有的点上往下加点时我们是不会加出折链的。故如果我们确定一个点为两个蓝线的中点,只有可能是它的父亲与他的一个儿子断开红线形成的。故我们可以用树形DP。定义
为i不为中心点时它和它的子树产生的最优答案(也就是蓝线长度之和),
为i是中心点时它和它的子树产生的最优答案。那么就有以下转移方程。(u为当前决策的点,v为u的儿子)
我们以每个点为根都DFS一遍即可。
关键代码:
void dfs(int now){
dp[now][1]=-2e9;//初值
dp[now][0]=0;
for(int i=head[now];i;i=edge[i].nx){
int nxt=edge[i].to;
if(fa[now]==nxt)continue;
fa[nxt]=now;
dfs(nxt);
dp[now][0]+=max(dp[nxt][0],dp[nxt][1]+edge[i].d);
}
for(int i=head[now];i;i=edge[i].nx){
int nxt=edge[i].to;
if(fa[now]==nxt)continue;
dp[now][1]=max(dp[now][1],dp[now][0]-max(dp[nxt][0],dp[nxt][1]+edge[i].d)+dp[nxt][0]+edge[i].d);
}
}
for(int i=1;i<=n;i++)fa[i]=0,dfs(i),ans=max(ans,dp[i][0]);//求答案
Part1
由于枚举根不行,又因为每一个节点的状态都是由它的子节点转移而来,故可以换根。
下面举个例子来解释算法流程。
假设有这样一颗树。
我们已经算出了以1为根的答案。现在我们我们要算以2为根的答案。那么我们就要减去2及其子树对 与 的贡献,那么得到新的 与 就是1,5,6三点的dp数组。再转移给2就好了,就可以求出以2为根的答案了。
减去贡献这个事,对于 来说只用减去 。但是对于 来说就不一样了。我们观察 的转移发现我们只用知道除2以外1的其他子节点的 就可以求出新的 。这个可以通过记一个最大值一个次大值解决,也可以用前后缀最大值。
注意如果这时从2转移到3,那么对于上面那个式子就还要考虑 (dp数组是新的),因为1是2的新的子节点,之前是没有考虑到的。
AC代码:(觉得看不懂代码就看看转移方程)
#include<cstdio>
#include<algorithm>
#include<vector>
#define M 200005
using namespace std;
struct E{
int to,nx,d;
}edge[M<<1];
int tot,head[M];
void Addedge(int a,int b,int d){
edge[++tot].to=b;
edge[tot].d=d;
edge[tot].nx=head[a];
head[a]=tot;
}
int fa[M];
int dp[M][2];
vector<int>Pre[M],Suf[M];
void check_max(int &x,int y){if(x<y)x=y;}
void dfs(int now){
dp[now][1]=-2e9;
dp[now][0]=0;
for(int i=head[now];i;i=edge[i].nx){
int nxt=edge[i].to;
if(fa[now]==nxt)continue;
fa[nxt]=now;
dfs(nxt);
dp[now][0]+=max(dp[nxt][0],dp[nxt][1]+edge[i].d);
}
Pre[now].clear();
Suf[now].clear();
for(int i=head[now];i;i=edge[i].nx){
int nxt=edge[i].to;
if(fa[now]==nxt)continue;
dp[now][1]=max(dp[now][1],dp[now][0]-max(dp[nxt][0],dp[nxt][1]+edge[i].d)+dp[nxt][0]+edge[i].d);
Pre[now].push_back(-max(dp[nxt][0],dp[nxt][1]+edge[i].d)+dp[nxt][0]+edge[i].d);//预处理前后缀最大值
Suf[now].push_back(-max(dp[nxt][0],dp[nxt][1]+edge[i].d)+dp[nxt][0]+edge[i].d);
}
for(int i=1;i<(int)Pre[now].size();i++)check_max(Pre[now][i],Pre[now][i-1]);
for(int i=(int)Suf[now].size()-2;i>=0;i--)check_max(Suf[now][i],Suf[now][i+1]);
}
int ans;
void redfs(int now,int lst0,int lst1,int dis){
dp[now][0]+=max(lst0,lst1+dis);//加上一个根对它的答案
dp[now][1]+=max(lst0,lst1+dis);//这个不用更新也可
dp[now][1]=max(dp[now][1],dp[now][0]-max(lst0,lst1+dis)+lst0+dis);
check_max(ans,dp[now][0]);//更新答案
int tot=-1;
for(int i=head[now];i;i=edge[i].nx){
int nxt=edge[i].to;
if(fa[now]==nxt)continue;
tot++;
int now_dp0=dp[now][0]-max(dp[nxt][0],dp[nxt][1]+edge[i].d);//新的dp0
int now_dp1=-max(lst0,lst1+dis)+lst0+dis;//***
if(tot!=0)check_max(now_dp1,Pre[now][tot-1]);//除了nxt外其他点的贡献
if(tot!=(int)Pre[now].size()-1)check_max(now_dp1,Suf[now][tot+1]);
redfs(nxt,now_dp0,now_dp0+now_dp1,edge[i].d);//注意新的dp1还要加上dp0(不理解看转移方程)
}
}
int main(){
//freopen("beads.in","r",stdin);
//freopen("beads.out","w",stdout);
int n;
scanf("%d",&n);
for(int i=1;i<n;i++){
int a,b,d;
scanf("%d%d%d",&a,&b,&d);
Addedge(a,b,d);
Addedge(b,a,d);
}
dfs(1);
redfs(1,0,-1e9,-1e9);//这里的初值不要太小,会爆int
printf("%d\n",ans);
return 0;
}