煙花表演是最引人注目的節日活動之一。在表演中,全部的煙花必須同時爆炸。爲了確保安全,煙花被安置在遠離開關的位置上,經過一些導火索與開關相連。導火索的鏈接方式造成一棵樹,煙花是樹葉,如圖 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; }