1. 程式人生 > >APIO2016 Fireworks

APIO2016 Fireworks

node 復合 begin href 存在 isp inf 根據 推出

APIO2016 Fireworks

題意:

題目傳送門

題解:

第一眼想到的應該是一個\(Dp\),我們記\(f_{u, i}\)表示\(u\)這個子樹中,所有葉子節點到\(u\)的距離都為\(i\)的最小代價。考慮這個\(Dp\)函數的形狀,發現在葉子節點中,這個\(Dp\)是呈\(V\)型的,在\(len_u\)取得最小值。仔細分析一下發現,幾個\(V\)型函數疊起來,也一定是一個下凸的函數,並且是由一段段斜率遞增的分段一次函數組成的。這裏我們規定這個下凸函數中相鄰的兩個一次函數之間的斜率相差\(1\),即使可能存在兩個區間\([a, b], [b, c]\)的斜率分別為\(1, 3\),我們也認為在\([b, b]\)

這個長度為\(0\)的區間中存在一個斜率為\(2\)的曲線。具體為什麽後面會提到。
我們考慮如何從點\(v\)轉移到他的父親\(u\),我們記\([L, R]\)為函數\(f_v\)中斜率為為\(0\)的區間,\(len\)\((u, v)\)的邊長,然後轉移方程如下:

\[ f_{u, i} = \begin{cases} f_{v, i} + len \ \ (i \leq L) \f_{v, L} + len - i + L \ \ (L \leq i \leq L +len) \f_{v, i - len} \ \ (L + len \leq i \leq R + len)\f_{v, R} + i - R - len \ \ (i \geq R + len) \\end{cases} \]

首先考慮第一個轉移\(f_{u, i} = f_{v, i} + len \ \ (i \leq L)\)
由於\([L, R]\)區間斜率為\(0\),所以當\(i \leq L\)時,所在函數的斜率一定小於\(0\),假設\(f_{u, i}\)是從\(f_{v, k}\)轉移過來的,由於\(k\)所在直線斜率小於\(0\),所以當轉移點從\(k\)變成\(k + 1\)的時候,減少的代價一定大於等於\(1\),而此時的邊長減少了\(1\),代價增加了\(1\),所以我們從\(k + 1\)轉移到\(i\)一定不會變劣,那麽最優的轉移點一定就是\(f_{v, i}\),那麽此時的新邊的邊長為\(0\)

,所以這次轉移的代價就是\(len\)

然後考慮$f_{u, i} = f_{v, l} + len - i + L ??(L \leq i \leq L +len) $
這個是比較顯然的,假設我們是從\(f_{v, k}\)轉移過來的,那麽新邊的邊長為\(i - k\),這次轉移的代價為\(len - i + k\)。而在\([L, L + len]\)區間內的\(f_{v}\)的值,一定是非遞減的,所以我們在\(k\)取最小為\(L\)的時候一定是最優的。

然後是$f_{u, i} = f_{v, i - len} (L + len \leq i \leq R +len) $
和第一種轉移差不多的方法來考慮,在\([L + len, R + len]\)區間中函數的斜率一定大於等於\(0\),假設\(f_{u, i}\)是從\(f_{v, k}\)轉移過來的,由於\(k\)所在直線斜率小於\(0\),所以當轉移點從\(k\)變成\(k - 1\)的時候,減少的代價一定大於等於\(1\),而此時的邊長增加了\(1\),代價減少了\(1\),所以從\(k - 1\)轉移到\(i\)一定是更優的,那麽最優轉移點為\(i - len\),此時新邊邊長為\(len\),轉移的代價為\(0\)

最後是\(f_{u, i} = f_{v, r} + i - R - len \ \ (i \geq R + len)\)
\([R + len, + \infty]\)區間中的斜率一定大於等於\(1\),所以此時轉移點從\(k\)變成\(k - 1\)的時候,減少的代價一定大於\(1\),而新邊的邊長增加了\(1\),所以此時的最優轉移點是\(R\),新邊的邊長為\(R + len\),轉移的代價為\(i - R - len\)

現在我們知道了這些轉移的方程了,考慮我們該怎麽快速維護這個轉移。觀察這些轉移的實質,第一個轉移實際上是把\([- \infty,L]\)中的所有點上移\(len\)的距離,第二個轉移實際上是在\([L, L + len]\)區間內插入一條斜率為\(-1\)的斜線,第三個轉移實際上是把\([L, R]\)區間右移\(len\)的長度,第四個轉移實際上是在\([R + len, + \infty]\)插入斜率為\(1\)的斜線。所以兩個\(V\)型函數的合並相當於將斜率為\(0\)的一段右移,然後插入兩端斜率為\(-1\)\(1\)的函數。發現這個東西在有了之前我們規定的下凸函數中相鄰的兩個一次函數之間的斜率相差\(1\)的性質之後就可以維護了。我們在一個堆中記錄當前函數的拐點,那麽每有一個拐點,當前斜率就加\(1\)

這樣就會有一個比較顯然的結論,就是每一個點的下凸函數的最大的斜率就是這個點的子節點個數。因為我們每次將子節點合並到父節點的時候,最右邊斜率大於\(1\)的點都會被彈出,所以我們每次只會把一個斜率最大值為\(1\)\(V\)型函數復合到父節點上,那麽一個點有多少個子節點,那麽最右邊的斜率就是多少。這樣每次彈出右邊斜率大於\(1\)的點,然後合並到父節點上,我們就可以得到根節點的\(V\)型函數了。這個過程可以用任意一個可並堆來維護。

得到了根節點的函數之後,我們考慮如何計算。首先\(f_0\)一定是\(\sum_u len_u\),即所有邊權之和。我們就希望得到\(f_L\)的值,這個值就是答案了。具體算法就是用\(f_0\)減去當前函數中所有斜率小於等於\(0\)的拐點的\(x\)坐標之和,證明如下:

我們記\(\{x_1, x_2, \cdots, x_n, L, R \}\)為當前根節點函數中所有拐點的\(x\)坐標,那麽根據每經過一個拐點,斜率就增加\(1\)的性質,我們可以推出\(f_0 - f_L = (L - x_n) \times 1 + (x_n - x_{n - 1}) \times 2 + \cdots (x_2 - x_1) \times (n - 1) + (x_1 - 0) \times n\),這個經過化簡可得\(f_0 - f_L = x_1 + x_2 + \cdots + x_n\),由此可以算出\(f_L\),這個即為答案。

Code

#pragma GCC optimize(2,"inline","Ofast")
#include <bits/stdc++.h>
using namespace std;
const int N = 3e5 + 50;
typedef long long ll;

namespace Heap {
  struct node {
    int ls, rs, dis;
    ll v;
  }t[N << 1];
  int rt[N];
  int tot; 
  int Merge(int x, int y) {
    if(!x || !y) return x | y;
    if(t[x].v < t[y].v) swap(x, y);
    t[x].rs = Merge(t[x].rs, y);
    t[x].dis = t[x].rs ? t[t[x].rs].dis + 1 : 0;
    if(!t[x].ls || t[t[x].ls].dis < t[t[x].rs].dis) swap(t[x].ls, t[x].rs);
    return x;
  }
  int Newnode(ll v) { t[++tot] = (node) { 0, 0, 0, v }; return tot; }
  void Pop(int &rt) { rt = Merge(t[rt].ls, t[rt].rs); }
  void Push(int Rt, ll v) { int o = Newnode(v); rt[Rt] = Merge(rt[Rt], o); }
}

int n, m;
ll ans = 0;
int sz[N], v[N], fa[N];

int main() {
  scanf("%d%d", &n, &m);
  for(int i = 2; i <= n + m; i++) scanf("%d%d", &fa[i], &v[i]), sz[fa[i]]++, ans += v[i];
  for(int i = n + 1; i <= n + m; i++) {
    Heap::Push(i, v[i]); Heap::Push(i, v[i]);
    Heap::rt[fa[i]] = Heap::Merge(Heap::rt[fa[i]], Heap::rt[i]);
  }
  for(int i = n; i >= 2; i--) {
    while(sz[i] > 1) sz[i]--, Heap::Pop(Heap::rt[i]);
    ll R = Heap::t[Heap::rt[i]].v; Heap::Pop(Heap::rt[i]);
    ll L = Heap::t[Heap::rt[i]].v; Heap::Pop(Heap::rt[i]);
    R = R + v[i], L = L + v[i];
    Heap::Push(i, L); Heap::Push(i, R);
    Heap::rt[fa[i]] = Heap::Merge(Heap::rt[fa[i]], Heap::rt[i]);
  }
  while(sz[1]) sz[1]--, Heap::Pop(Heap::rt[1]); 
  while(Heap::rt[1]) ans -= Heap::t[Heap::rt[1]].v, Heap::Pop(Heap::rt[1]);
  printf("%lld\n", ans);
  return 0;
}

APIO2016 Fireworks