树上的巧克力-树形DP

树上的巧克力-树形DP

原题

题意

在树上选取两条不重叠的链,使得链上的权值和最大。

思路

很容易想到和树的直径有关,但是我们有两条链,这怎么办?

我们先观察它和直径的关系,发现画出来的图无非是一条直径和一条去掉直径后的直径(和第一条直径连起来也是剩下的直径)。

pZ9bFQU.md.png

所以,我们先求出直径,然后在每个直径上的点求出去掉直径后的直径。(去掉直径就是不能走这条直径),简单算一下时间复杂度,发现在 \(O(n^2)\) 的外表下藏着 \(O(n)\) 的心,可行。

然后就可以快乐度过水样例,发现 \(wa\) 在了第 \(3\) 个点。

仔细考虑一下我们的思路和代码实现,再加上 \(cf\) 大善人送的第 \(3\) 个样例,发现我们的少了两条链都不是直径的情况,这是什么玩意,不是偏离了我们的思路吗?但细细思考会发现,依旧有直径的身影。

pZ9bQSK.md.png

至于求出这样新的两边,我们可以记录直径上每个节点先连且不走直径的最大链。最后最一遍前缀和即可。

实在想不到的话可以用 \(dp\) 把每种状态记录,但是我浪费的时间已经很多了,就不详细展开。

code

#include <bits/stdc++.h>
#define int long long
using namespace std;
constexpr int maxn = 1e5+10;

vector<int> gra[maxn];
int n,wi[maxn],di[maxn],c;
int pre[maxn],zj[maxn];// 记录直径节点
bool vis[maxn];

int line[maxn];// 节点出去的最大链
int pre_sum[maxn],ma_pre[maxn];
int suf_sum[maxn],ma_suf[maxn];

void dfs(int u,int p,int op=1)// 能记录前驱的dfs
{
    if(op==2)
    {
        pre[u]=p;
    }
    for(int v : gra[u])
    {
        if(v==p || vis[v]) continue;
        di[v]=di[u]+wi[v];
        if(di[v]>di[c])
        {
            c=v;
        }
        dfs(v,u,op);
    }
}

signed main()
{
    #ifndef ONLINE_JUDGE
    freopen("cjdl.in","r",stdin);
    freopen("cjdl.out","w",stdout);
    #endif // ONLINE_JUDGE

    scanf("%lld",&n);
    for(int i=1;i<=n;++i)
    {
        scanf("%lld",wi+i);
    }
    for(int i=1,x,y ;i<n;++i)
    {
        scanf("%lld%lld",&x,&y);
        gra[x].emplace_back(y);
        gra[y].emplace_back(x);
    }

    int ans1=0,ans2=0;

    di[1]=wi[1];// 求一遍直径,并记录节点
    dfs(1,0);
    di[c]=wi[c];
    dfs(c,0,2);
    ans1+=di[c];// ans1加上直径

    zj[++zj[0]]=c;
    vis[c]=1;
    while(pre[c])
    {
        c=pre[c];
        zj[++zj[0]]=c;
        vis[c]=1;// 标记
    }

    int ma=0;
    for(int i=1;i<=zj[0];++i)
    {
        c=zj[i];// 其实没用
        di[c]=0;// 好像也没用
        dfs(c,0);
        if(c==zj[i]) continue;// 如果不能出去
        line[zj[i]]=di[c];// line就是第一遍dfs
        di[c]=wi[c];
        dfs(c,0);
        ma=max(ma,di[c]);// 记录最长
    }

    ans1+=ma;

    for(int i=1;i<=zj[0];++i)
    {
        pre_sum[i]=pre_sum[i-1]+wi[zj[i]];// 直径权值
        ma_pre[i]=max(ma_pre[i-1],pre_sum[i]+line[zj[i]]);// 算上扩展的line的最大权值
    }
    for(int i=zj[0];i>=1;--i)
    {
        suf_sum[i]=suf_sum[i+1]+wi[zj[i]];
        ma_suf[i]=max(ma_suf[i+1],suf_sum[i]+line[zj[i]]);
    }
    for(int i=0;i<=zj[0];++i)
    {
        ans2=max(ans2,ma_pre[i]+ma_suf[i+1]);// 取最大
    }

    printf("%lld",max(ans1,ans2));

    return 0;
}
posted @ 2025-11-11 10:13  玖玮  阅读(9)  评论(0)    收藏  举报