題解[CF932F] Escape Through Leaf
阿新 • • 發佈:2021-07-21
題目描述
一棵有n個節點的樹,每個節點有兩個權值 \(a\)i 和 \(b\)i 從\(i\)點只能走到\(i\)子樹內的點,從\(i\)到\(j\)的花費為 \(a\)i 和 \(b\)j 的乘積
求從根到葉子節點的最短路徑長度(其中1為根)
\(n\) \(\leq\) 100000, |\(a\)i|, |\(b\)i| \(\leq\) 100000
分析
首先來一個正常人都不會這樣想的暴力:
我們暴力列舉每一個點\(i\),然後暴力列舉\(i\)子樹內的點,再暴力算出從\(i\)到子樹內每個點的花費,然後暴力將其連邊,接著暴力地將每個葉子節點向一個輔助點連一條權值為0的邊,最後暴力地以1為起點,那個輔助點為終點,跑暴力的SPFA演算法(因為有負邊), 複雜度也是十分暴力的\(O(n\)
當然如果他不卡SPFA(相信也沒人會卡)的話,複雜度可以達到十分優秀的\(O(kn\)\(2\)\()\)
當然如果你發現了這其實是一個DAG,複雜度其實可以達到\(O(n\)\(2\)\()\)
好的胡扯到此結束,我們現在來考慮正解
首先正常人一眼過去肯定會發現這題可以DP,我們設\(f\)\(i\)為根節點1到i節點的最小花費
那麼我們有
如果我們暴力按照上面的轉移方程來做的話,你會發現他就是上面提到的DAG最短路(DAG最短路的實質其實就是DP)
我們再觀察一下這個柿子
不難發現,其中\(a\)\(i\)只和i有關,\(b\)\(j\)與\(f\)\(j\)只和j有關
那麼\(f_i\)其實就是求多條直線\(y = b_j * x + f_j\)在\(x = a_i\)處的最大值
那這就是很經典的李超線段樹問題了
但由於是樹形DP,故此題還需要合併李超線段樹(其實就是普通的線段樹合併啦
程式碼
#include<bits/stdc++.h> #define int LL using namespace std; typedef long long LL; const int N = 200010; const int inf = 1e17; struct line{ double k, b; }p[N << 2]; int tree[N << 5], ls[N << 5], rs[N << 5], n, tot, head[N], nxt[N], to[N], a[N], b[N], root[N], cnt; long long dp[N]; int calc(int id, int d) { return p[id].k * d + p[id].b; } inline int read(){ int x = 0, f = 1; char c = getchar(); while(c < '0' || c > '9') { if(c == '-') f = -1; c = getchar(); } while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar(); return x * f; } void ins(int l, int r, int &x, int u) { if(!x) { x = ++tot; tree[x] = u; return ; } int v = tree[x], mid = (l + r) / 2, resu = calc(u, mid), resv = calc(v, mid); if(l == r) { if(resu < resv) tree[x] = u; return ; } if(!v) { tree[x] = u; return ; } if(p[u].k > p[v].k){ if(resu < resv){ tree[x] = u; ins(mid + 1, r, rs[x], v); } else ins(l, mid, ls[x], u); } else if(p[u].k < p[v].k){ if(resu < resv){ tree[x] = u; ins(l, mid, ls[x], v); } else ins(mid + 1, r, rs[x], u); } else if(resu < resv) tree[x] = u; } LL que(int l, int r, int x, int d) { if(l > d || r < d || !x) return inf; if(l == r) return calc(tree[x], d); int mid = (l + r) / 2; return min(1ll * calc(tree[x], d), min(que(l, mid, ls[x], d), que(mid + 1, r, rs[x], d))); } int merge(int x, int y, int l, int r) { if(!x || !y) return x + y; int tx = tree[x], ty = tree[y], mid = (l + r) / 2; if(l == r) return calc(tx, mid) < calc(ty, mid) ? x : y; ls[x] = merge(ls[x], ls[y], l, mid); rs[x] = merge(rs[x], rs[y], mid + 1, r); ins(l, r, x, tree[y]); return x; } void addedge(int u, int v) { to[++cnt] = v; nxt[cnt] = head[u], head[u] = cnt; } void dfs(int u, int f) { bool flag = false; for(int i = head[u]; i; i = nxt[i]){ int v = to[i]; if(v == f) continue; flag = true; dfs(v, u); root[u] = merge(root[u], root[v], -1e5, 1e5); } dp[u] = que(-1e5, 1e5, root[u], a[u]); if(!flag) dp[u] = 0; p[u].k = b[u], p[u].b = dp[u]; ins(-1e5, 1e5, root[u], u); } signed main() { n = read(); for(int i = 1; i <= n; i++) a[i] = read(); for(int i = 1; i <= n; i++) b[i] = read(); int u, v; for(int i = 1; i < n; i++){ u = read(), v = read(); addedge(u, v); addedge(v, u); } dfs(1, 0); for(int i = 1; i <= n; i++) printf("%lld ", dp[i]); return 0; }
需要注意的是,如果線性DP出現\(f_i = min(b_j * a_i + c_j)\)的轉移方程,我們應該首先考慮斜率優化(即\(a_i\)是否單調)