题解:P6615 草莓 Strawberry
NegetiveEdgeWeight · · 题解
题解:P6615 草莓 Strawberry
题目传送门
这篇题解的篇幅较长,如有不懂的地方,欢迎私信向作者提问,作者在正常情况下会在 1~3 天内回复。
题意
题目要你找一个树意义下的连通分量
思路
这道题很适合沿着部分分开始做。
:::info[Subtask 5]
直接输出
首先以一个点
如果我们设定最小跳跃距离为
只要
那么,所有满足
那么就存在一个贪心解法:计算所有点的
首先,树上距离任意节点
所以,我们可以在这条直径上完成 Subtask 4 的操作,定义
仅仅知道
考虑链的情况:如果
因为
树的情况:假设我们选取了一个阈值
情况 A:
情况 B:
对于任意点
对于任意点
由于
时间复杂度
正解具体实现 & 代码
:::info[实现]
- 找到直径端点
L ,R 。 - 计算所有点到
L 和R 的距离dL_i,dR_i 。 - 计算
val_i = \max(dL_i, dR_i) 。 - 将所有点按
val_i 降序排序。 - 枚举排序后的前
k 个点作为集合S 。这里要处理L 和R 是否连通的边界情况(即瓶颈长度是否大于直径)。 ::: :::success[代码] 代码丑。#include<bits/stdc++.h> #define For(a,b,c) for(int a=b;a<=c;a++) #define pb push_back #define int long long using namespace std; const int N=220005; int n,a[N],dL[N],dR[N],p[N],ans,sum; struct Edge{ int v,w; }; vector<Edge> g[N]; int bfs(int s,int* d){ For(i,1,n)d[i]=-1; d[s]=0; queue<int> q; q.push(s); int f=s; while(q.size()){ int u=q.front(); q.pop(); if(d[u]>d[f])f=u; for(auto [v,w]:g[u])if(d[v]==-1){ d[v]=d[u]+w; q.push(v); } } return f; } //处理单侧贪心的情况的函数 void solve(int* d){ sum=0; For(i,1,n){ sum+=a[p[i]]; ans=max(ans,sum*d[p[i]]); } } signed main(){ cin>>n; For(i,1,n) cin>>a[i]; For(i,2,n){ int u,v,w; cin>>u>>v>>w; g[u].pb({v,w}); g[v].pb({u,w}); } //三次 BFS 确定直径及其端点 L,R int L=bfs(1,dL),R=bfs(L,dL); bfs(R,dR); iota(p+1,p+1+n,1); //相当于 For(i,1,n) p[i]=i; //连通块仅挂在直径的一端 (L 或 R) //此时不通过直径跨越,退化为以 L 或 R 为根的单链贪心 sort(p+1,p+1+n,[&](int x,int y){return dL[x]>dL[y];}); solve(dL); sort(p+1,p+1+n,[&](int x,int y){return dR[x]>dR[y];}); solve(dR); //连通块利用直径作为桥梁 (跨越 L-R) sort(p+1,p+1+n,[&](int x,int y){return max(dL[x],dR[x])>max(dL[y],dR[y]);}); sum=0; int diam=dL[R]; For(i,1,n){ sum+=a[p[i]]; //若距离 W > 直径,则无法跨越,退化为单侧(已被策略A覆盖) //此有效距离取 min(点到端点最大距离, 直径长度) ans=max(ans,sum*min(max(dL[p[i]],dR[p[i]]),diam)); } cout<<ans; return 0; }:::