Description
给你一棵树,每个点有权,求出树中两条不相交的路径,使得两个路径上的点权总和最大
输出这个最大值
对于100%的数据,n<=200000,0<=ai<=1000000000(1e9)
Analysis
这道题有多种方法,可以DP,用f[i][0/1]来乱搞
这是一个神奇的根据直径性质的方法
默认直径是横着放的
首先求出直径,再求出不与直径相交的最长路径,求和作为答案
求出直径上每个点左右两侧的最长路径(设为l[v],r[v]):注意这个最长路径可能是延伸到直径该侧端点,也可能延伸到这个点某个子树下面
这是可以O(n)求的
然后在直径上随机选u,v(u在v左边),用l[u]+r[v]更新答案
求前后缀最大值就能做到O(n)了
Code
#include<cstdio>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,b,a) for(int i=b;i>=a;i--)
#define efo(i,v) for(int i=last[v];i;i=next[i])
using namespace std;
typedef long long ll;
const int N=200010,M=N*2;
int n,m,tot,u1,u2,v1,v2,d[N];
ll mx,ans,a[N],b[N],c[N],sl[N],sr[N],l[N],r[N],ml[N],mr[N];
int fa[N],f[N],g[N],to[M],next[M],last[N];
bool tag,bz[N];
void link(int u,int v)
{
to[++tot]=v,next[tot]=last[u],last[u]=tot;
}
void dfs1(int v,int fr,ll k)
{
if(k>mx) mx=k,v1=v;
efo(i,v)
{
int u=to[i];
if(u==fr || bz[u]) continue;
dfs1(u,v,k+a[u]);
}
}
void dfs2(int v,int fr,ll k)
{
if(k>mx) mx=k,v2=v;
if(!tag) fa[v]=fr;
efo(i,v)
{
int u=to[i];
if(u==fr || bz[u]) continue;
dfs2(u,v,k+a[u]);
}
}
void get(int i)
{
mx=0;dfs1(i,i,a[i]);
mx=0;dfs2(v1,v1,a[v1]);
ll t1=0,t2=0;
for(int v=v1;;)
{
t1+=a[v];
if(v==i) break;
v=fa[v];
}
for(int v=v2;;)
{
t2+=a[v];
if(v==i) break;
v=fa[v];
}
b[i]=max(t1,t2);
if(v1==v2) c[i]=t1;
else c[i]=t1+t2-a[i];
}
int main()
{
int u,v;
scanf("%d",&n);
fo(i,1,n) scanf("%d",&a[i]);
fo(i,1,n-1)
{
scanf("%d %d",&u,&v);
link(u,v),link(v,u);
}
dfs1(1,1,a[1]);mx=0;
dfs2(v1,v1,a[v1]);
u1=v1,u2=v2;
ll disu=0,disv=0;
for(int u=u2;;)
{
bz[u]=1,disu+=a[u];
d[++m]=u;
if(u==u1) break;
u=fa[u];
}
tag=1;
fo(k,1,m)
{
int u=d[k];
efo(j,u)
{
int i=to[j];
if(bz[i]) continue;
get(i);
disv=max(disv,c[i]);
}
get(u);
}
ans=disv+disu;
fo(k,1,m)
{
int u=d[k];
sl[k]=sl[k-1]+a[d[k]];
l[k]=max(c[u],b[u]+sl[k-1]);
ml[k]=max(ml[k-1],l[k]);
}
fd(k,m,1)
{
int u=d[k];
sr[k]=sr[k+1]+a[d[k]];
r[k]=max(c[u],b[u]+sr[k+1]);
mr[k]=max(mr[k+1],r[k]);
ans=max(ans,mr[k]+ml[k-1]);
}
printf("%lld",ans);
return 0;
}