烟花表演是最引人注目的节日活动之一。在表演中,全部的烟花必须同时爆炸。为了确保安全,烟花被安置在远离开关的位置上,经过一些导火索与开关相连。导火索的链接方式造成一棵树,烟花是树叶,如图 1所示。火花从开关出发,沿导火索移动。每当火花抵达一个分叉点时,它会扩散到与之相连的全部导火索,继续燃烧。导火索燃烧的速度是一个固定常数。图 1展现了六枚烟花 ${E_1, E_2, \ldots, E_6 }$ 的连线布局,以及每根导火索的长度。图中还标注了当在时刻 $0$ 从开关点燃火花时,每一发烟花的爆炸时间。c++
<center>图 1</center>安全
<br>函数
Hyunmin 为烟花表演设计了导火索的连线布局。不幸的是,在他设计的布局中,烟花不必定同时爆炸。咱们但愿修改一些导火索的长度,让全部烟花在同一时刻爆炸。例如,为了让图 1中的全部烟花在时刻 $13$ 爆炸,咱们能够像图 2中左边那样调整导火索长度。相似地,为了让图 1中的全部烟花在时刻 $14$ 爆炸,咱们能够像图 2中右边那样调整长度。布局
<center> 图 2</center>spa
<br>.net
修改导火索长度的代价等于修改先后长度之差的绝对值。例如,将图 1中布局修改成图 2,左边布局的总代价为 $6$,而将图 1中布局修改成图 2右边布局的总代价为 $5$。设计
导火索的长度能够被减为 $0$,同时保持连通性不变。code
给定一个导火索的连线布局,你须要编写一个程序,去调整导火索长度,让全部的烟花在同一时刻爆炸,并使得代价最小。blog
全部的输入均为正整数。令 $N$ 表明分叉点的数量,$M$ 表明烟花的数量。分叉点从 $1$ 到 $N$ 编号,编号为 $1$ 的分叉点是开关。烟花从 $N+1$ 到 $N+M$ 编号。get
$N::M$
$P_2::C_2$
$P_3::C_3$
$\ldots$
$P_N::C_N$
$P_{N+1}::C_{N+1}$
$\ldots$
$P_{N+M}::C_{N+M}$
其中 $P_i$ 知足 $1\le P_i<i$,表明和分叉点或烟花 $i$ 相连的分叉点。$C_i$ 表明链接它们的导火索长度 $(1\le C_i\le 10^9)$。除开关外,每一个分叉点和多于 $1$ 条导火索相连,而每发烟花刚好与 $1$ 条导火索相连。
子任务 1(7 分):$N=1,1 \le M \le 100$。
子任务 2(19 分):$1 \le N+M \le 300$,且开关到任一烟花的距离不超过 $300$。
子任务 3(29 分):$1 \le N+M \le 5000$。
子任务 4(45 分):$1 \le N+M \le 3\times 10^5$。
$\$
设$f_i(x)$为使得$i$的子树中全部叶子到$i$距离为$x$所付出的代价。很明显$f_i(x)$是个下凸的函数,并且中间有一整段斜率为$0$的区间,假设为$[L,R]$。咱们先考虑已经获得了$f_u(x)$,要加上$u$的父亲到$u$的那条边(长度为$w$)后函数怎么变化。 $$ f_{fa_u}(x)= \begin{cases} f_u(x)+w & x\leq L\ f_u(L)+w-(x-L) & L\leq x\leq L+w\ f_u(L) & L+w <x\leq R+w\ f_u(R)+x-w-R & R+w<x \end{cases} $$ 假设最终这条边的边权为$w'$,最优策略就是尽可能使得$x-w'$,也就是全部叶子到$u$的距离尽可能往$[L,R]$靠。
咱们观察这个转移,至关于将$L$以左的部分抬高$w$,而后接一条斜率为$-1$的直线,而后接一条斜率为$0$的直线,最后接斜率为$1$的直线。也就是说先将右端的斜率大于$0$的直线所有删除,再加入上述三条直线。
假设咱们已经完成了这个操做,因而就把这个函数加到$fa_u$的函数中。咱们发现累加函数会使斜率逐渐增大。好比函数$1$在$x\geq x_1$的部分斜率为$1$,函数$2$在$x\geq x_2$的部分斜率为$1$($x_1<x_2$),那么新函数在$x_1\leq x\leq x_2$的部分斜率为$1$,在$x>x_2$的部分斜率为$2$。
因此咱们只须要维护函数的拐点的横坐标就好了。考虑每合并一个儿子,函数的最大斜率都会$+1$,因此函数最右端斜率$>0$的端点个数就是儿子的数量。具体实现能够用可并堆。
最后考虑获得最终的函数后怎么算答案。明显答案就在那一段斜率为$0$的区间,可是咱们只维护了横坐标。咱们知道$f_1(0)=sum$,其中$sum$表示全部的边长度之和。每次合并函数,最大斜率$+1$,同理最小斜率也会$-1$,全部函数左侧的斜率也是递减的。假设最左侧斜率$<0$的一堆端点有$k$个,分别为$x_1,x_2,\ldots,x_k$,那么答案就 $$ sum-x_1k+\sum_{i=2}^k(x_i-x_{i-1})(k-i+1)\ =sum-\sum_{i=1}^kx_i $$
代码:
#include<bits/stdc++.h> #define ll long long #define N 600005 using namespace std; inline int Get() {int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}while('0'<=ch&&ch<='9') {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}return x*f;} int n,m; struct tree { int ls,rs,key; ll val; }tr[N<<1]; int Merge(int a,int b) { if(!a||!b) return a+b; if(tr[a].val<tr[b].val) swap(a,b); tr[a].rs=Merge(tr[a].rs,b); if(tr[tr[a].ls].key<tr[tr[a].rs].key) swap(tr[a].ls,tr[a].rs); tr[a].key=tr[a].rs?tr[tr[a].rs].key+1:0; return a; } int Pop(int a) {return Merge(tr[a].ls,tr[a].rs);} int fa[N]; int len[N]; ll sum; int sn[N]; int rt[N]; int tot; int main() { n=Get(),m=Get(); for(int i=2;i<=n+m;i++) { fa[i]=Get(),len[i]=Get(); sum+=len[i]; sn[fa[i]]++; } for(int i=n+m;i>=2;i--) { ll l=0,r=0; if(i<=n) { while(--sn[i]) rt[i]=Pop(rt[i]); r=tr[rt[i]].val;rt[i]=Pop(rt[i]); l=tr[rt[i]].val;rt[i]=Pop(rt[i]); } tr[++tot].val=l+len[i]; tr[++tot].val=r+len[i]; rt[i]=Merge(rt[i],Merge(tot-1,tot)); rt[fa[i]]=Merge(rt[fa[i]],rt[i]); } while(sn[1]--) rt[1]=Pop(rt[1]); while(rt[1]) { sum-=tr[rt[1]].val; rt[1]=Pop(rt[1]); } cout<<sum; return 0; }