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