树上的巧克力-树形DP
树上的巧克力-树形DP
题意
在树上选取两条不重叠的链,使得链上的权值和最大。
思路
很容易想到和树的直径有关,但是我们有两条链,这怎么办?
我们先观察它和直径的关系,发现画出来的图无非是一条直径和一条去掉直径后的直径(和第一条直径连起来也是剩下的直径)。
所以,我们先求出直径,然后在每个直径上的点求出去掉直径后的直径。(去掉直径就是不能走这条直径),简单算一下时间复杂度,发现在 \(O(n^2)\) 的外表下藏着 \(O(n)\) 的心,可行。
然后就可以快乐度过水样例,发现 \(wa\) 在了第 \(3\) 个点。
仔细考虑一下我们的思路和代码实现,再加上 \(cf\) 大善人送的第 \(3\) 个样例,发现我们的少了两条链都不是直径的情况,这是什么玩意,不是偏离了我们的思路吗?但细细思考会发现,依旧有直径的身影。
至于求出这样新的两边,我们可以记录直径上每个节点先连且不走直径的最大链。最后最一遍前缀和即可。
实在想不到的话可以用 \(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;
}



浙公网安备 33010602011771号