【JZOJ 4923】 巧克力狂欢 树的直径经典问题

58 篇文章 0 订阅
3 篇文章 0 订阅

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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值