【AtCoder Grand Contest 007 E】Shik and Travel

二分答案,双指针。

题目大意:

给定一棵带权有根二叉树,$1$ 为根结点。每个非叶子结点有两个儿子。

现在你要从 $1$ 出发,走到一个叶结点。然后每次从一个叶结点走到另一个叶结点。最后从叶结点返回 $1$。要求每条边恰好经过 $2$ 次,每个点都经过。

定义一种遍历方案的代价为,除了 $1$ 出发和 $1$ 停止的路径,其他的叶结点到叶结点的路径中,路径长度最长的那一条的长度。

求这个长度的最小值。

题解:

最小化最大值,考虑二分答案。

对于一个非叶子结点,由于它有且仅有两个儿子,所以它一定是选择一个儿子向下走,然后从另一个儿子走上来。

我们令三元组 $(x,l,r)$ 表示当前结点为 $x$,有一种方案,其从一个儿子走到底部的链的长度为 $l$,从另一个儿子的子树底部走上来的链的长度为 $r$,并且其中间的路径的长度都不超过当前二分的答案 $ans$。

对于两个三元组 $(x,l_1,r_1)$ 和 $(x,l_2,r_2)$,若 $l_1\leq l_2,r_1\leq r_2$,则显然保留第一个三元组就可以了。

考虑合并结点 $x$ 的两个结点的儿子的三元组,以获得当前结点的三元组。

由于 $l$ 单调递增,$r$ 单调递减,所以我们可以用双指针合并两个三元组。

两个三元组 $(son_1,l_1,r_1)$ 和 $(son_2,l_2,r_2)$ 若满足 $r_1+l_2\leq ans$(即从左边上来从右边下去形成完整的路径不超过答案),则可以合并为 $(x,l_1,r_2)$。

每层的合并复杂度为 $O(n)$。

通过启发式合并的分析方式可得,每次的复杂度为 $O(n\log n)$。

外面有二分答案,所以总时间复杂度 $O(n\log^2 n)$。

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