解题思路c++
又是一道 solpe trick 题,观察出图像变化后不找一些性质仍是挺难作的。git
首先令 \(dp[u][i]\) 为节点 \(u\) 极其子树全部叶子到 \(u\) 距离为 \(i\) 的最少代价,显然有
\[ dp[u][i]=\sum_{v\in son(u)}\min_{0\leq j \leq i}\{dp[v][j]+|C(u,v)-(i-j)|\} \]
定义函数
\[ f_u(x)=dp[u][x] , g_u(x)= \min_{0\leq i\leq x}\{dp[u][x]+|C(fa[u],u)-(x-i)|\} \]
能够获得 \(f_u(x) = \sum_{v \in son(u)} g_v(x)\) 。api
不难证实,\(f,g\) 的图像都是一个下凸包,且相邻的段之间斜率变化为 \(1\) ,考虑由 \(f_u(x)\) 到 \(g_u(x)\) 的过程函数
令 \(L, R\) 为 \(f_u\) 最下面那条边的左右端点,\(len=C(fa[u],u)\) ,把过程看作对图像的操做,那么有:
\[ g_u(x)= \begin{cases} f_u(x)+len& x<L \\ f_u(L)+len-(x-L) &L\leq x<L+len \\ f_u(L) & L + len \leq x\leq R+len \\ f_u(R)+(x-R)-len & x>R+len \end{cases} \]
考虑维护这个凸包的拐点,1,2,3操做合起来至关于删除凸包上 \(L,R\) 两个拐点,而后插入 \(L+len,R+len\) 这两个拐点。spa
4操做只须要在以前把斜率 \(\geq1\) 的拐点删除到只剩一个便可,不难证实对于非叶子节点,这样的拐点只有儿子数量 \(-1\) 个,删完以后要找的 \(L,R\) 就是当前最右边的两个拐点。code
那么对于每个 \(u\) 只须要将全部儿子的 \(g\) 合并起来便可获得当前的 \(f\) ,可并堆/线段树合并实现都是 \(\mathcal O((n+m)\log n)\)。get
咱们维护出来 \(f_1\) 的全部拐点以后,求答案只须要用 \(f_1(0)\) 的值减去全部斜率 \(\leq0\) 的拐点的横坐标便可。it
/*program by mangoyang*/ #include <bits/stdc++.h> #include <ext/pb_ds/priority_queue.hpp> #define inf (0x7f7f7f7f) #define Max(a, b) ((a) > (b) ? (a) : (b)) #define Min(a, b) ((a) < (b) ? (a) : (b)) typedef long long ll; using namespace std; template <class T> inline void read(T &x){ int f = 0, ch = 0; x = 0; for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = 1; for(; isdigit(ch); ch = getchar()) x = x * 10 + ch - 48; } const int N = 1000005; ll ans; int fa[N], len[N], deg[N], n, m; __gnu_pbds::priority_queue<ll> pq[N]; int main(){ read(n), read(m); for(int i = 2; i <= n + m; i++){ read(fa[i]), deg[fa[i]]++; read(len[i]), ans += len[i]; } for(int i = n + m; i > 1; i--){ ll x = 0, y = 0; if(i <= n){ for(int j = 1; j < deg[i]; j++) pq[i].pop(); x = pq[i].top(), pq[i].pop(); y = pq[i].top(), pq[i].pop(); } pq[fa[i]].push((ll) x + len[i]); pq[fa[i]].push((ll) y + len[i]); pq[fa[i]].join(pq[i]); } for(int i = 1; i <= deg[1]; i++) pq[1].pop(); while(!pq[1].empty()) ans -= pq[1].top(), pq[1].pop(); cout << ans << endl; return 0; }