題解 洛谷 P3642 【[APIO2016]煙火表演】
阿新 • • 發佈:2020-08-06
設 \(f_i(x)\) 為以節點 \(i\) 為根的子樹都以時刻 \(x\) 爆炸的最小代價,發現其為一個下凸的分段函式,即為一個下凸包。
考慮一個節點加上其與父節點的邊後函式的變化,設原函式的最小值為 \(f_{\min}\),取到最小值的區間為 \([l,r]\),與父節點的邊的邊權為 \(v\),得:
\[{f}'(x) = \begin{cases} f(x) + v & x \leqslant l \\ f_{\min} + l + v - x & l < x \leqslant l + v \\ f_{\min} & l + v < x \leqslant r + v \\ f_{\min} + x - r - v & r + v < x \end{cases} \]
發現和原函式對比,加上這條邊後,函式的變化為向上平移左邊的一段,然後加入三段斜率分別為 \(-1,0,1\) 的函式。對於一個節點,其所有兒子的新函式合併後的函式,即為該節點對應的函式。因為是合併起來的,所以該分段函式的每一段的斜率的差為 \(1\),最小值右邊的函式段數為其兒子個數。
對於函式,只需維護其端點位置即可,因為需要合併和刪除,所以用可並堆來維護,我這裡用的是左偏樹。得 \(f_{root}(0)\) 為所有邊權和,結合端點位置,即可計算最小值。
\(code:\)
#include<bits/stdc++.h> #define maxn 600010 using namespace std; typedef long long ll; template<typename T> inline void read(T &x) { x=0;char c=getchar();bool flag=false; while(!isdigit(c)){if(c=='-')flag=true;c=getchar();} while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();} if(flag)x=-x; } int n,m,tot; ll ans; int fa[maxn],rt[maxn],ls[maxn],rs[maxn],dis[maxn],son[maxn]; ll v[maxn],val[maxn]; int merge(int x,int y) { if(!x||!y) return x+y; if(val[x]<val[y]) swap(x,y); rs[x]=merge(rs[x],y); if(dis[ls[x]]<dis[rs[x]]) swap(ls[x],rs[x]); if(rs[x]) dis[x]=dis[rs[x]]=1; else dis[x]=0; return x; } int del(int x) { return merge(ls[x],rs[x]); } int main() { read(n),read(m); for(int i=2;i<=n+m;++i) read(fa[i]),read(v[i]),son[fa[i]]++,ans+=v[i]; for(int i=n+m;i>1;--i) { ll l=0,r=0; if(son[i]) { for(int j=1;j<son[i];++j) rt[i]=del(rt[i]); l=val[rt[i]],rt[i]=del(rt[i]); r=val[rt[i]],rt[i]=del(rt[i]); } val[++tot]=l+v[i],rt[i]=merge(rt[i],tot); val[++tot]=r+v[i],rt[i]=merge(rt[i],tot); rt[fa[i]]=merge(rt[fa[i]],rt[i]); } while(son[1]--) rt[1]=del(rt[1]); while(rt[1]) ans-=val[rt[1]],rt[1]=del(rt[1]); printf("%lld",ans); return 0; }