1. 程式人生 > 其它 >【APIO2016】Fireworks【閔可夫斯基和】【凸包向量和】【可並堆】

【APIO2016】Fireworks【閔可夫斯基和】【凸包向量和】【可並堆】

題意:給一棵帶邊權的樹,可以花費 1 1 1 的代價把一條邊的邊權修改 1 1 1,一條邊可以修改多次,求使得根到葉子距離相等的最小代價。

n ≤ 3 × 1 0 5 n\leq 3\times 10^5 n3×105

先暴力 dp

f ( u , k ) f(u,k) f(u,k) 表示 u u u 到子樹內所有葉子距離為 k k k 的最小代價。因為仍然有多種,所以最小是有意義的。

加入一個兒子 v v v 後,設這條邊長度為 x x x

f ( u , k ) ⟵ f ( u , k ) + min ⁡ { ∣ i − x ∣ + f ( v , k − i ) } f(u,k)\longleftarrow f(u,k)+\min\{|i-x|+f(v,k-i)\}

f(u,k)f(u,k)+min{ix+f(v,ki)}

發現這東西就是 ∣ x − i ∣ |x-i| xi f v f_v fv 做閔可夫斯基和,所以歸納法證明是個凸包。

然後 ∣ x − i ∣ |x-i| xi 這個東西相當於是一段 ( x , − x ) (x,-x) (x,x) 的向量和斜率為 1 1 1 的一條射線,因為凸包斜率都是整數,相當於找到斜率為 0 0 0 的這一段,將它和它右邊的向右下分別移動 x x x,然後右邊替換為斜率為 1 1 1 的射線。

然後是後面的凸包向量和,我們按從大到小維護凸包的所有頂點的橫座標,並規定每經過一個點斜率會減小 1 1

1,顯然最開始的斜率是 1 1 1。那麼凸包向量和就是所有頂點的可重並集。(一個點出現兩次說明斜率變化了 2 2 2

用可並堆維護即可。最後得到了根的凸包,它的截距是所有邊的和,然後就可以推出答案。

複雜度 O ( n log ⁡ n ) \Omicron(n\log n) O(nlogn)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cctype>
#include <vector>
#define MAXN 600005
using namespace
std; typedef long long ll; inline int read() { int ans=0; char c=getchar(); while (!isdigit(c)) c=getchar(); while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar(); return ans; } int ch[MAXN][2],fa[MAXN],tot; ll val[MAXN]; int find(int x){return fa[x]==x? x:fa[x]=find(fa[x]);} int merge(int x,int y) { if (!x||!y) return x|y; if (val[x]<val[y]) swap(x,y); fa[ch[x][1]=merge(ch[x][1],y)]=x; swap(ch[x][0],ch[x][1]); return x; } inline void insert(int& x,int v){val[++tot]=v,x=merge(x,tot);} inline void erase(int& x){fa[ch[x][0]]=ch[x][0],fa[ch[x][1]]=ch[x][1],x=fa[x]=merge(ch[x][0],ch[x][1]);} vector<int> e[MAXN]; int c[MAXN],rt[MAXN]; void dfs(int u) { if (!e[u].size()) return insert(rt[u],c[u]),insert(rt[u],c[u]); for (int i=0;i<(int)e[u].size();i++) { dfs(e[u][i]); rt[u]=merge(rt[u],rt[e[u][i]]); } for (int i=0;i<(int)e[u].size()-1;i++) erase(rt[u]); int x,y; x=rt[u],erase(rt[u]); y=rt[u],erase(rt[u]); val[x]+=c[u],val[y]+=c[u]; ch[x][0]=ch[x][1]=ch[y][0]=ch[y][1]=0; fa[x]=x,fa[y]=y; rt[u]=merge(rt[u],x); rt[u]=merge(rt[u],y); } int main() { int n=read()+read(); ll sum=0; for (int i=1;i<=n;i++) fa[i]=i; for (int i=2;i<=n;i++) e[read()].push_back(i),sum+=c[i]=read(); dfs(1); int las=rt[1],k=0; while (rt[1]) { erase(rt[1]); int cur=rt[1]; sum-=k*(val[las]-val[cur]); las=cur,++k; } cout<<sum; return 0; }