【AtCoder Grand Contest 007 E】Shik and Travel
二分答案,双指针。
题目大意:
给定一棵带权有根二叉树,1 为根结点。每个非叶子结点有两个儿子。
现在你要从 1 出发,走到一个叶结点。然后每次从一个叶结点走到另一个叶结点。最后从叶结点返回 1。要求每条边恰好经过 2 次,每个点都经过。
定义一种遍历方案的代价为,除了 1 出发和 1 停止的路径,其他的叶结点到叶结点的路径中,路径长度最长的那一条的长度。
求这个长度的最小值。
题解:
最小化最大值,考虑二分答案。
对于一个非叶子结点,由于它有且仅有两个儿子,所以它一定是选择一个儿子向下走,然后从另一个儿子走上来。
我们令三元组 (x,l,r) 表示当前结点为 x,有一种方案,其从一个儿子走到底部的链的长度为 l,从另一个儿子的子树底部走上来的链的长度为 r,并且其中间的路径的长度都不超过当前二分的答案 ans。
对于两个三元组 (x,l1,r1) 和 (x,l2,r2),若 l1≤l2,r1≤r2,则显然保留第一个三元组就可以了。
考虑合并结点 x 的两个结点的儿子的三元组,以获得当前结点的三元组。
由于 l 单调递增,r 单调递减,所以我们可以用双指针合并两个三元组。
两个三元组 (son1,l1,r1) 和 (son2,l2,r2) 若满足 r1+l2≤ans(即从左边上来从右边下去形成完整的路径不超过答案),则可以合并为 (x,l1,r2)。
每层的合并复杂度为 O(n)。
通过启发式合并的分析方式可得,每次的复杂度为 O(nlogn)。
外面有二分答案,所以总时间复杂度 O(nlog2n)。
Code:
#include<iostream>
#include<vector>
using namespace std;
typedef long long LL;
const int N=131072;
int ch[N][2],t0t[N],fa[N],n,V[N];
typedef vector<pair<LL,LL> >VPII;
void merge(VPII&L,VPII&R,VPII&vec,const LL&lim){
for(int i=0,it=0;i<L.size();++i){
while(it+1<R.size()&&L[i].second+R[it+1].first<=lim)++it;
if(it==R.size())break;
if(L[i].second+R[it].first<=lim)vec.push_back(make_pair(L[i].first,R[it++].second));
}
}
void dfs(int now,LL lim,VPII&vec){
if(t0t[now]==0){
if(V[now]<=lim)vec.push_back(make_pair(V[now],V[now]));
return;
}
VPII L,R,Lv,Rv;
dfs(ch[now][0],lim,L),dfs(ch[now][1],lim,R);
merge(L,R,Lv,lim),merge(R,L,Rv,lim);
int it1=0,it2=0;
while(it1<Lv.size()&&it2<Rv.size())
if(Lv[it1].first<Rv[it2].first){
vec.push_back(Lv[it1]);
++it1;
}else
if(Lv[it1].first>Rv[it2].first){
vec.push_back(Rv[it2]);
++it2;
}else{
Lv[it1].second=min(Lv[it1].second,Rv[it2].second);
vec.push_back(Lv[it1]);
++it1,++it2;
}
while(it1<Lv.size()){
vec.push_back(Lv[it1]);
++it1;
}
while(it2<Rv.size()){
vec.push_back(Rv[it2]);
++it2;
}
for(auto&i:vec)i.first+=V[now],i.second+=V[now];
}
bool check(LL x){
VPII vp;
dfs(1,x,vp);
return!vp.empty();
}
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n;
for(int i=2;i<=n;++i){
cin>>fa[i];
cin>>V[i];
ch[fa[i]][t0t[fa[i]]++]=i;
}
LL l=0,r=1145141919810,ans=r;
while(l<=r){
const LL mid=l+r>>1;
if(check(mid))r=(ans=mid)-1;else l=mid+1;
}
cout<<ans<<'\n';
return 0;
}
Gitalking ...