AGC007 E

来源:互联网 发布:c语言的符号函数 编辑:程序博客网 时间:2024/05/22 17:21

题意:
给出一棵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_backusing 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