JZOJ4923. 【NOIP2017提高组模拟12.17】巧克力狂欢

本篇介绍了一道算法题目,通过树形DP的方法寻找一棵树上两条不相交链的最大巧克力总和,详细解析了两种情况的处理思路,并提供了完整的C++代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description

Alice和Bob有一棵树(无根、无向),在第i个点上有ai个巧克力。首先,两人个选择一个起点(不同的),获得点上的巧克力;接着两人轮流操作(Alice先),操作的定义是:在树上找一个两人都没选过的点并获得点上的巧克力,并且这个点要与自己上一次选的点相邻。当有一人无法操作 时,另一个人可以继续操作,直到不能操作为止。因为Alice和Bob是好朋友,所以他们希望两人得到的巧克力总和尽量大,请输出最大总和。

Input

第一行一个整数n,表示树的点数
第二行有n个整数,表示每个点上的巧克力数量ai
接下来n-1行,每行两个整数u、v,表示u和v之间有一条边。

Output

输出一个整数,表示两人能获得得巧克力的最大总和。

Sample Input

9
1 2 3 4 5 6 7 8 9
1 2
1 3
1 4
1 5
1 6
1 7
1 8
1 9

Sample Output

25

Data Constraint

对于20%的数据,n<=15
对于40%的数据,n<=100
对于60%的数据,n<=5000
对于100%的数据,n<=200000,0<=ai<=1000000000(1e9)

分析

题目的意思就是要找两条不相交的链,使它们的和最大。
就分两种情况进行讨论。

第一种情况,其中一条链与树的直径不相交,那么也就是说,
两条链分别就是一条直径,和一条与直径不相交的最长链。
处理起来也比较简单,
先找一条直径,这条直径会将这棵树分成很多部分,只要在每一部分找一下最长链就可以了。

第二种情况,就麻烦一点,两条链都与直径有公共部分。
这里写图片描述
已知中间那条链是直径(点权省略),直径的右端部分与其中一条链(红色部分)相交,
现在我们对另一条链进行选择,
一种方案是选择绿色加上蓝色,另一种方案是选择绿色和黄色。根据直径的性质可以知道黄色的部分一定大于或者等于蓝色的部分,否则黄色的部分就不应该是直径。
从中我们可以得出一个结论,如果某一条链与直径相交,那么这条链最长的情况就应该是链的一个端点在直径的端点上面。
也就是说,这两条链的组成就是从直径的左端点开始,到直径上的某个点,然后就这条链离开直径,另外一条链的组成也大致相同,只要保证它们在直径上面不相交就可以了。
处理起来也比较简单,
首先先找到一条直径,然后以直径上的每一个点开始,找一条不与直径相交的最长链,然后对这些链之间进行两两匹配。
而在两两匹配的时候,从直径的左端向右端枚举,记录左端的最大值,用这个最大值和当前这个点匹配,然后用当前这个点去更新这个最大值。

最后就将两种情况合并一下就可以了。

code

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <string.h>
#include <cmath>
#include <math.h> 
#define N 200003
#define ll long long
using namespace std;
int a[2*N],next[2*N],b[N],m,w[N],t[N];
ll f[N],ans,v[N],tt,g,mx,ans1,sum;
bool bz[N];
int n,x,y;
void ins(int x,int y)
{
    next[++m]=b[x];
    a[m]=y;
    b[x]=m;
}
void dg(int x,int fa,ll s)
{
    for(int i=b[x];i;i=next[i])
        if((a[i]!=fa)&&(bz[a[i]]))dg(a[i],x,s+v[a[i]]);
    if(s>tt)tt=s,g=x;
}
void dg4(int x,int fa,ll s)
{
    bz[x]=0;
    for(int i=b[x];i;i=next[i])
        if((a[i]!=fa)&&(bz[a[i]]))dg4(a[i],x,s+v[a[i]]);
    if(s>tt)tt=s,g=x;
}
void dg1(int y,int x,int fa,ll s)
{
    for(int i=b[x];i;i=next[i])
        if(a[i]!=fa)
        {
            w[y]=a[i];
            dg1(y+1,a[i],x,s+v[a[i]]);
        }
    if(s>=tt)
    {
        tt=s;
        g=y-1;
        memcpy(t,w,sizeof(t));
    }
}
void dg3(int y,int x,int fa,ll s)
{
    for(int i=b[x];i;i=next[i])
        if(a[i]!=fa)dg3(y,a[i],x,s+v[a[i]]);
    if(s>f[y])f[y]=s;
}
int main()
{
    memset(bz,1,sizeof(bz));
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%lld",&v[i]);
    for(int i=1;i<n;i++)
    {
        scanf("%d%d",&x,&y);
        ins(x,y);
        ins(y,x);
    }
    dg(1,0,v[1]);
    w[1]=g;
    dg1(2,g,0,v[g]);
    for(int i=1;i<=g;i++)
    {
        bz[t[i]]=0;
        for(int j=b[t[i]];j;j=next[j])
            if((a[j]!=t[i-1])&&(a[j]!=t[i+1]))dg3(i,a[j],t[i],v[a[j]]);
        if(f[i]+tt-sum+mx>ans)ans=f[i]+tt-sum+mx;
        sum=sum+v[t[i]];
        if(f[i]+sum>mx)mx=f[i]+sum;
    }
    mx=0;
    for(int i=1;i<=n;i++)
    {
        if(bz[i])
        {
            tt=g=0;
            dg(i,0,v[i]);
            dg4(g,0,v[g]);
            mx=max(mx,tt);
        }
    }
    ans=max(ans,sum+mx);
    printf("%lld",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值