Loj #2568. 「APIO2016」煙花表演

Loj #2568. 「APIO2016」煙花表演

題目描述

煙花表演是最引人注目的節日活動之一。在表演中,全部的煙花必須同時爆炸。爲了確保安全,煙花被安置在遠離開關的位置上,經過一些導火索與開關相連。導火索的鏈接方式造成一棵樹,煙花是樹葉,如圖 1所示。火花從開關出發,沿導火索移動。每當火花抵達一個分叉點時,它會擴散到與之相連的全部導火索,繼續燃燒。導火索燃燒的速度是一個固定常數。圖 1展現了六枚煙花 ${E_1, E_2, \ldots, E_6 }$ 的連線佈局,以及每根導火索的長度。圖中還標註了當在時刻 $0$ 從開關點燃火花時,每一發煙花的爆炸時間。c++

圖 1

<center>圖 1</center>安全

<br>函數

Hyunmin 爲煙花表演設計了導火索的連線佈局。不幸的是,在他設計的佈局中,煙花不必定同時爆炸。咱們但願修改一些導火索的長度,讓全部煙花在同一時刻爆炸。例如,爲了讓圖 1中的全部煙花在時刻 $13$ 爆炸,咱們能夠像圖 2中左邊那樣調整導火索長度。相似地,爲了讓圖 1中的全部煙花在時刻 $14$ 爆炸,咱們能夠像圖 2中右邊那樣調整長度。佈局

圖 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;
}
相關文章
相關標籤/搜索