AGC007 E

题意:
给出一棵n个节点树,除了叶节点,每个节点恰好有两个孩子。边上有边权。第一天根开始走,每天选一个叶节点,从当前点走到叶节点,最后一天走回根节点。要求每条边经过两次,每个叶节点被选一次。花费就是除了第一天和最后一天走的路程最远那一天的路程。问最小花费。
2< n< 131,072

#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<iostream>
#include<vector>
#define N 150000
#define LL long long
#define pb push_back
using namespace std;
struct node{int y,d,nex;}a[2*N];
struct node1{LL a,b;}A[N],B[N];
vector<node1> v[N];
int n,fir[N],len,f[N],du[N],al,bl;
LL ans,inf=1ll<<50,lim,g[N];
void ins(int x,int y,int d)
{
    a[++len].y=y;a[len].d=d;a[len].nex=fir[x];fir[x]=len;
}
void dfs(int x,int fa)
{
     if(du[x]==1) {v[x].pb((node1){0,0});return;}
     int y1=0,y2=0,s1,s2,i1,i2;LL L,mi;
     for(int k=fir[x];k;k=a[k].nex)
     {
         int y=a[k].y;
         if(y==fa) continue;
         f[y]=a[k].d;
         dfs(y,x);
         if(y1==0) y1=y;
         else y2=y;
     }
     s1=v[y1].size();s2=v[y2].size();
     if(s1==0 || s2==0) return;
     if(s1>s2) swap(s1,s2),swap(y1,y2);
     L=lim-f[y1]-f[y2];
     al=bl=0;

     mi=inf;i2=0;
     for(i1=0;i1<s1;i1++)
     {
         while(v[y1][i1].b+mi>L && i2<s2) {mi=min(mi,v[y2][i2].a);i2++;}
         if(v[y1][i1].b+mi>L) break;
         A[++al]=(node1){v[y1][i1].a+f[y1],v[y2][i2-1].b+f[y2]};
     }

     for(int i=0;i<s1;i++) g[i]=inf;
     mi=inf;i1=0;
     for(i2=0;i2<s2;i2++)
     {
         while(v[y2][i2].b+mi>L && i1<s1) {mi=min(mi,v[y1][i1].a);i1++;}
         if(v[y2][i2].b+mi>L) break;
         g[i1-1]=min(g[i1-1],v[y2][i2].a+f[y2]);
     }
     for(int i=0;i<s1;i++) if(g[i]!=inf) B[++bl]=(node1){g[i],v[y1][i].b+f[y1]};

     i1=1;i2=1;
     while(i1<=al || i2<=bl)
     {
         node1 t;
         if(i2>bl) t=A[i1++];
         else if(i1>al || A[i1].b>B[i2].b) t=B[i2++];
         else t=A[i1++];
         v[x].pb(t);
     }
}
bool check()
{
    for(int i=1;i<=n;i++) v[i].clear();
    dfs(1,0);
    if(v[1].size()) return 1;
    return 0;
}
int main()
{
    LL l=0,r=0;
    scanf("%d",&n);
    for(int i=2;i<=n;i++)
    {
        int y,d;scanf("%d%d",&y,&d);
        ins(i,y,d);ins(y,i,d);
        du[i]++;du[y]++;
        r+=d;
    }
    while(l<=r)
    {
        LL mid=(l+r)/2;
        lim=mid;
        if(check()) ans=mid,r=mid-1;
        else l=mid+1;
    }
    printf("%lld\n",ans);
    return 0;
}

题解:
膜了题解。。
先二分答案lim
考虑一个暴力做法,我们对每个节点x为根的子树,维护一堆的二元组(a,b)表示存在一种方案,第一天从x出发路程为a,最后一天回到x,路程为b。s[x]表示x的二元组数量。size[x]表示x子树大小。
设x的两个孩子是y1,y2,x到y1的边是t1,x到y2的边是t2。
对于y1的(a,b),y2的(x,y)
如果b+t1+x+t2<=lim,就可以合并为(a+t1,y+t2)。
如果y+t2+a+t1<=lim,就可以合并为(x+t2,b+t1)。
设s[y1]<=s[y2],那么我们可以对每个(a+t1,…)和(…,b+t1)找最优的另一部分,维护二元组一维有序双指针查找,然后归并就能线性完成。
这时,有s[x]<=2*s[y1]
由于总的运算量关于 ni=1s[i] 是线性的
而且显然s[x]<=size[x],那么这个复杂度就和每个节点遍历非最大子树相当,像cf那题一样分析就有 O(nlogn)
所以总复杂度 O(nlog2n)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值