【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),若 l1l2,r1r2,则显然保留第一个三元组就可以了。

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

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

两个三元组 (son1,l1,r1)(son2,l2,r2) 若满足 r1+l2ans(即从左边上来从右边下去形成完整的路径不超过答案),则可以合并为 (x,l1,r2)

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

通过启发式合并的分析方式可得,每次的复杂度为 O(nlogn)

外面有二分答案,所以总时间复杂度 O(nlog2n)

Code:

  1. #include<iostream>
  2. #include<vector>
  3. using namespace std;
  4. typedef long long LL;
  5. const int N=131072;
  6. int ch[N][2],t0t[N],fa[N],n,V[N];
  7. typedef vector<pair<LL,LL> >VPII;
  8. void merge(VPII&L,VPII&R,VPII&vec,const LL&lim){
  9. for(int i=0,it=0;i<L.size();++i){
  10. while(it+1<R.size()&&L[i].second+R[it+1].first<=lim)++it;
  11. if(it==R.size())break;
  12. if(L[i].second+R[it].first<=lim)vec.push_back(make_pair(L[i].first,R[it++].second));
  13. }
  14. }
  15. void dfs(int now,LL lim,VPII&vec){
  16. if(t0t[now]==0){
  17. if(V[now]<=lim)vec.push_back(make_pair(V[now],V[now]));
  18. return;
  19. }
  20. VPII L,R,Lv,Rv;
  21. dfs(ch[now][0],lim,L),dfs(ch[now][1],lim,R);
  22. merge(L,R,Lv,lim),merge(R,L,Rv,lim);
  23. int it1=0,it2=0;
  24. while(it1<Lv.size()&&it2<Rv.size())
  25. if(Lv[it1].first<Rv[it2].first){
  26. vec.push_back(Lv[it1]);
  27. ++it1;
  28. }else
  29. if(Lv[it1].first>Rv[it2].first){
  30. vec.push_back(Rv[it2]);
  31. ++it2;
  32. }else{
  33. Lv[it1].second=min(Lv[it1].second,Rv[it2].second);
  34. vec.push_back(Lv[it1]);
  35. ++it1,++it2;
  36. }
  37. while(it1<Lv.size()){
  38. vec.push_back(Lv[it1]);
  39. ++it1;
  40. }
  41. while(it2<Rv.size()){
  42. vec.push_back(Rv[it2]);
  43. ++it2;
  44. }
  45. for(auto&i:vec)i.first+=V[now],i.second+=V[now];
  46. }
  47. bool check(LL x){
  48. VPII vp;
  49. dfs(1,x,vp);
  50. return!vp.empty();
  51. }
  52. int main(){
  53. ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
  54. cin>>n;
  55. for(int i=2;i<=n;++i){
  56. cin>>fa[i];
  57. cin>>V[i];
  58. ch[fa[i]][t0t[fa[i]]++]=i;
  59. }
  60. LL l=0,r=1145141919810,ans=r;
  61. while(l<=r){
  62. const LL mid=l+r>>1;
  63. if(check(mid))r=(ans=mid)-1;else l=mid+1;
  64. }
  65. cout<<ans<<'\n';
  66. return 0;
  67. }

Gitalking ...