P6058 [加油武漢]體溫調查:二分答案+最近公共祖先

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);

}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章