Codeforces 932.F Escape Through Leaf
You are given a tree with n nodes (numbered from 1 to n) rooted at node 1. Also, each node has two values associated with it. The values for i-th node are ai and bi.
You can jump from a node to any node in its subtree. The cost of one jump from node x to node y is the product of ax and by. The total cost of a path formed by one or more jumps is sum of costs of individual jumps. For every node, calculate the minimum total cost to reach any leaf from that node. Pay attention, that root can never be leaf, even if it has degree 1.
Note that you cannot jump from a node to itself.
The first line of input contains an integer n (2 ≤ n ≤ 10^5) — the number of nodes in the tree.
The second line contains n space-separated integers a1, a2, ..., an( - 10^5 ≤ ai ≤ 10^5).
The third line contains n space-separated integers b1, b2, ..., bn( - 10^5 ≤ bi ≤ 10^5).
Next n - 1 lines contains two space-separated integers ui and vi (1 ≤ ui, vi ≤ n) describing edge between nodes ui and vi in the tree.
Output n space-separated integers, i-th of which denotes the minimum cost of a path from node i to reach any leaf.
3
2 10 -1
7 -7 5
2 3
2 1
10 50 0
4
5 -10 5 7
-8 -80 -3 -10
2 1
2 4
1 3
-300 100 0 0
In the first example, node 3 is already a leaf, so the cost is 0. For node 2, jump to node 3 with cost a2 × b3 = 50. For node 1, jump directly to node 3 with cost a1 × b3 = 10.
In the second example, node 3 and node 4 are leaves, so the cost is 0. For node 2, jump to node 4 with cost a2 × b4 = 100. For node 1, jump to node 2 with cost a1 × b2 = - 400 followed by a jump from 2 to 4 with cost a2 × b4 = 100.
题目大意:每个点有两个点权a,b,一个点i可以向它子树的任意一个点j中跳去,代价为ai * bj.求出每个点跳到任意一个叶子节点的最小代价.
分析:这道题还是比较难的.
先考虑一个dp: f[i] = min{f[j] + a[i] * b[j]}. 复杂度是O(n^2),过不了,需要进行优化.
观察这个式子,它长得特别像斜率优化一般式子,所以能不能考虑斜率优化呢? 显然也是不行的吧,这是在子树上操作,并不是在序列上操作.
斜率优化虽然不行,但是可以借鉴它的思想:将b[j]当作斜率,f[j]当作截距. 对于每一个j,都有一条对应的直线. 那么题目就变成了在每个点i的子树中的点j表示的直线中,找到一条,使得f[j] + a[i] * b[j]最大. 这个可以利用线段树标记永久化来解决.
现在最棘手的一个问题就是:怎么样才能使得线段树只记录当前点的子树的信息呢?
可以用dsu on tree! 使用dsu on tree的条件是没有修改操作,并且只涉及到子树内的查询. 恰好满足. 但是dsu on tree会有一个清楚答案的过程,怎么把一个线段树上记录的信息给清除呢? 动态加点即可,这就相当于又重建了一棵线段树.
挺好的一道题,将斜率优化的思想,标记永久化,dsu on tree都给考了一遍. 能够运用这些方法的题目的特征要记清楚!
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; typedef long long ll; const ll maxn = 100010,inf = (1LL<<55),maxx = 100000; ll n,a[maxn],b[maxn],K[maxn << 2],B[maxn << 2],ans[maxn],cnt,root,tr[maxn << 2]; ll head[maxn],to[maxn * 2],nextt[maxn * 2],tot = 1,sizee[maxn],son[maxn],lson[maxn << 2],rson[maxn << 2]; void add(ll x,ll y) { to[tot] = y; nextt[tot] = head[x]; head[x] = tot++; } void dfs(ll u,ll fa) { sizee[u] = 1; for (ll i = head[u];i;i = nextt[i]) { ll v = to[i]; if (v == fa) continue; dfs(v,u); sizee[u] += sizee[v]; if (sizee[v] > sizee[son[u]]) son[u] = v; } } double jiao(ll x,ll y) { return (double)(B[x] - B[y]) / (K[y] - K[x]); } void insert(ll &tott,ll l,ll r,ll v) { if (!tott) { tott = ++cnt; lson[tott] = rson[tott] = 0; tr[tott] = v; return; } ll mid = (l + r) >> 1; ll l1 = B[v] + K[v] * l,l2 = B[v] + K[v] * r; ll r1 = B[tr[tott]] + K[tr[tott]] * l,r2 = B[tr[tott]] + K[tr[tott]] * r; if (l1 >= r1 && l2 >= r2) return; if (l1 <= r1 && l2 <= r2) tr[tott] = v; else { double X = jiao(tr[tott],v); if (l1 >= r1) { if (X <= mid) insert(lson[tott],l,mid,tr[tott]),tr[tott] = v; else insert(rson[tott],mid + 1,r,v); } else { if (X > mid) insert(rson[tott],mid + 1,r,tr[tott]),tr[tott] = v; else insert(lson[tott],l,mid,v); } } return; } ll get(ll x,ll y) { return K[x] * y + B[x]; } ll getmin(ll tott,ll l,ll r,ll pos) { if (!tott) return inf; ll ans = get(tr[tott],pos); ll mid = (l + r) >> 1; if (pos <= mid) ans = min(ans,getmin(lson[tott],l,mid,pos)); else ans = min(ans,getmin(rson[tott],mid + 1,r,pos)); return ans; } void solve2(ll sx,ll u,ll fa) { ans[sx] = min(ans[sx],ans[u] + b[u] * a[sx]); insert(root,-maxx,maxx,u); for (ll i = head[u];i;i = nextt[i]) { ll v = to[i]; if (v == fa) continue; solve2(sx,v,u); } } void solve(ll u,ll fa) { if (!son[u]) //如果是叶子节点 { K[u] = b[u]; B[u] = 0; insert(root,-maxx,maxx,u); return; } for (ll i = head[u];i;i = nextt[i]) //先计算轻儿子 { ll v = to[i]; if (v == son[u] || v == fa) continue; solve(v,u); } root = cnt = 0; //消除轻儿子的贡献 solve(son[u],u); //计算重儿子的贡献 ans[u] = getmin(root,-maxx,maxx,a[u]); for (ll i = head[u];i;i = nextt[i]) { ll v = to[i]; if (v == son[u] || v == fa) continue; solve2(u,v,u); //计算轻儿子的答案 } K[u] = b[u]; B[u] = ans[u]; insert(root,-maxx,maxx,u); //插入当前线段 } int main() { scanf("%I64d",&n); for (ll i = 1; i <= n; i++) scanf("%I64d",&a[i]); for (ll i = 1; i <= n; i++) scanf("%I64d",&b[i]); for (ll i = 1; i < n; i++) { ll x,y; scanf("%I64d%I64d",&x,&y); add(x,y); add(y,x); } dfs(1,0); solve(1,0); for (ll i = 1; i <= n; i++) printf("%I64d ",ans[i]); return 0; }