https://www.luogu.com.cn/problem/P6058
題目大意,求樹上根節點到分段連續葉節點的距離的最大值最小。看到最大值最小,當然先往二分答案上去考慮了。
怎樣求連續葉節點的距離呢?如下圖所示dis[4]+(dis[6]-dis[lca(4,6)]+...
因此我們需要求出連續葉節點的Lca.一般情況下的LCA可以用倍增或者tarjan,但這個有點特殊,他是連續的葉節點的lca,我們在dfs的過程中只需要記錄回溯後的深度最小的點,那麼這個點就是其lca。因此我們可以僅用一次dfs求出需要的葉節點,葉節點到根節點的距離,相鄰葉節點的LCA。
在做題的過程中總得40分,就以爲自己LCA寫錯了,換了倍增求,還是一樣的錯。那就是二分的檢查函數寫錯了,查了很久才發現,分段檢查的時候忘了判斷初始的dis[]是否符合要求。
還有一個問題,下面得代碼開氧氣優化會RE,可是本蒟蒻不知道爲什麼。
//二分答案,枚舉距離檢查。
//記錄每個葉子節點最近的前一個葉子節點。,求每個節點與其相鄰的葉子
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=3e5+10;
vector<pair<int,int> >g[N];
int n,k,Lca[N],Deep[N],mindep=1e9,minpoint=0;//記錄其比他小的最近的節點v,和v的最近公共祖先。
int num=0,lef[N];
ll dis[N];
int dfs(int u,int fa,int dp,ll sumw) {
dis[u]=sumw,Deep[u]=dp;
for(int i=0; i<g[u].size(); i++) {
int v=g[u][i].first;
ll w=g[u][i].second;
if(v==fa)continue;
if(g[v].size()==1) {
Lca[v]=minpoint;
mindep=1e9;
lef[++num]=v;
}
dfs(v,u,dp+1,sumw+w);
}
if(mindep>Deep[u])mindep=Deep[u],minpoint=fa;
}
bool check(ll x) { //計算距離x需要分幾段。
int cnt=1;
ll sum=dis[lef[1]];
for(int i=2; i<=num; i++) {
if(sum>x)return 0;//一開始沒想到這句,總得40分:(
int v=lef[i],u=Lca[v];
if(sum+dis[v]-dis[u]<=x)sum+=dis[v]-dis[u];
else
cnt++,sum=dis[v];
if(cnt>k)return 0;//需要的人手多餘k個,這個時間太短了,增加時間
}
return cnt<=k; //時間還可以更少
}
int main() {
scanf("%d%d",&n,&k);
int u,v,w;
ll l=0,r=0,mid,ans;
for(int i=1; i<n; i++) {
scanf("%d%d%d",&u,&v,&w);
g[u].push_back(make_pair(v,w));
g[v].push_back(make_pair(u,w));
r+=(ll)w;
}
dfs(1,0,0,0);
while(l<=r) {
mid=(l+r)/2;
if(check(mid))
ans=mid,r=mid-1;
else l=mid+1;
}
ans*=2;
printf("%lld\n",ans);
}