分析
我们可以先写出暴力转移方程 表示 节点到子树中某个叶子节点的最小代价。那么转移方程为 可以看出这个是类似 的直线方程的,但是斜率并不单调,所以考虑李超线段树,但是从一个子树转移到另一个子树不能暴力转移,否则时间复杂度为 。所以我们考虑线段树合并,处理完子树后进行线段树合并。这样时间复杂度为 ,有大佬通过势能分析,时间复杂度为 。最后跑的还是挺快。
代码
#include<bits/stdc++.h> using namespace std; #define LL long long const LL N = 2e6 + 100,M = 1e5,inf = 0x3f3f3f3f3f3f3f3f; LL a[N],b[N],n,f[N]; LL t[N],lc[N],rc[N],size,rt[N]; LL read() {LL x;scanf("%lld",&x);return x;} LL g(LL x,LL p) {return b[x] * p + f[x];} vector<LL> G[N]; void insert(LL &u,LL l,LL r,LL id) { if(!u) {u = ++size;t[u] = id;return;} LL mid = l + r >> 1; LL val_l = g(t[u],l),val_r = g(t[u],r),val_L = g(id,l),val_R = g(id,r); if(!t[u] || val_R <= val_r && val_L <= val_l) {t[u] = id;return;} if(val_l <= val_L && val_r <= val_R) {return;} insert(lc[u],l,mid,id);insert(rc[u],mid+1,r,id); } LL ask(LL u,LL l,LL r,LL p) { if(!u) return inf; if(l == r) {return g(t[u],p);} LL ans = g(t[u],p);LL mid = l + r >> 1; if(p <= mid) return min(ans,ask(lc[u],l,mid,p)); else return min(ans,ask(rc[u],mid+1,r,p)); } LL merge(LL x,LL y,LL l,LL r) { if(!x || !y) return x|y; int mid = l + r >> 1; insert(x,l,r,t[y]); lc[x] = merge(lc[x],lc[y],l,mid);rc[x] = merge(rc[x],rc[y],mid+1,r); return x; } void solve(LL x,LL fa) { int son = 0; for(auto y:G[x]) { if(y == fa) continue; solve(y,x); rt[x] = merge(rt[x],rt[y],-M,M); } f[x] = ask(rt[x],-M,M,a[x]); if(f[x] == inf) f[x] = 0; insert(rt[x],-M,M,x); } int main() { n = read();f[0] = inf; for(LL i = 1;i <= n;i++) a[i] = read(); for(LL i = 1;i <= n;i++) b[i] = read(); for(LL i = 1;i < n;i++) { LL a = read(),b = read(); G[a].push_back(b);G[b].push_back(a); } solve(1,0); for(LL i = 1;i <= n;i++) printf("%lld ",f[i]); printf("\n"); }