CF932F Escape Through Leaf 题解
首先考虑暴力 dp,设 \(f_{x}\) 表示 \(x\) 走到叶子节点的答案,那么枚举 \(x\) 子树内节点 \(u\),就有:\(f_{x} = \min\{f_u+B_u \times A_x\}\)
注意到该式在 \(f_u\) 确定的时候其实是个一次函数 \(B_ux+f_u\) 的形式,所以我们可以考虑李超线段树。
然后注意到该式枚举的节点 \(u\) 可以是 \(x\) 子树内任意一个点,因此考虑李超线段树 + 线段树合并。
具体而言,每个点开一个李超线段树,每次计算 \(f_x\) 时,首先做线段树合并,然后查询求出 \(f_x\),最后往线段树中插入 \(k=B_x,b=f_x\) 这样的一条直线。
至于线段树合并过程,考虑将两个节点合并的时候(比如 \(p2\) 合并到 \(p1\)),直接将 \(p2\) 节点维护的线段往 \(p1\) 中插入即可,显然易得这样的复杂度不会超过 \(O(n \log^2 n)\)(线段树合并 \(O(n \log n)\),单次插入直线 \(O(\log n)\)),但是有大佬说这样复杂度其实是 \(O(n \log n)\) 的,证明用到了势能分析,但是正式做题时只需要知道复杂度不超过 \(O(n \log^2 n)\) 即可。
GitHub:CodeBase-of-Plozia
Code:
/*
========= Plozia =========
Author:Plozia
Problem:CF932F Escape Through Leaf
Date:2022/7/15
========= Plozia =========
*/
#include <bits/stdc++.h>
typedef long long LL;
const int MAXN = 2e5 + 5, LEFT = -1e5, RIGHT = 1e5;
int n, Head[MAXN], cntEdge, cntsgt, Root[MAXN], Size[MAXN], cntline;
LL a[MAXN], b[MAXN], f[MAXN];
struct LINE { LL k, b; } Line[MAXN];
struct node { int To, Next; } Edge[MAXN << 1];
struct SgT { int tag, ls, rs; } tree[MAXN * 40];
#define tag(p) tree[p].tag
#define ls(p) tree[p].ls
#define rs(p) tree[p].rs
int Read()
{
int sum = 0, fh = 1; char ch = getchar();
for (; ch < '0' || ch > '9'; ch = getchar()) fh -= (ch == '-') << 1;
for (; ch >= '0' && ch <= '9'; ch = getchar()) sum = sum * 10 + (ch ^ 48);
return sum * fh;
}
void addEdge(int x, int y) { ++cntEdge; Edge[cntEdge] = (node){y, Head[x]}; Head[x] = cntEdge; }
LL Calc(int l, LL x) { return (l == 0) ? 0x3f3f3f3f3f3f3f3fll : (Line[l].k * x + Line[l].b); }
LL Min(LL fir, LL sec) { return (fir < sec) ? fir : sec; }
void Insert(int &p, int k, int lp, int rp)
{
if (!p) p = ++cntsgt;
if (tag(p) == 0) { tag(p) = k; return ; }
if (lp == rp)
{
if (Calc(tag(p), lp) > Calc(k, lp)) tag(p) = k;
return ;
}
int mid = (lp + rp) >> 1;
if (Line[tag(p)].k > Line[k].k)
{
if (Calc(tag(p), mid) > Calc(k, mid)) { Insert(ls(p), tag(p), lp, mid); tag(p) = k; }
else Insert(rs(p), k, mid + 1, rp);
}
else if (Line[tag(p)].k < Line[k].k)
{
if (Calc(tag(p), mid) > Calc(k, mid)) { Insert(rs(p), tag(p), mid + 1, rp); tag(p) = k; }
else Insert(ls(p), k, lp, mid);
}
else if (Calc(tag(p), mid) > Calc(k, mid)) tag(p) = k;
}
void Merge(int &p1, int p2, int lp, int rp)
{
if (!p1 || !p2) { p1 = p1 + p2; return ; }
if (lp == rp) { if (Calc(tag(p2), lp) < Calc(tag(p1), lp)) tag(p1) = tag(p2); return ; }
int mid = (lp + rp) >> 1; Merge(ls(p1), ls(p2), lp, mid); Merge(rs(p1), rs(p2), mid + 1, rp);
Insert(p1, tag(p2), lp, rp);
}
LL Ask(int &p, LL x, LL lp, LL rp)
{
if (!p) p = ++cntsgt; if (lp == rp) return Calc(tag(p), x);
int mid = (lp + rp) >> 1; LL val = 0x7f7f7f7f7f7f7f7f;
if (x <= mid) val = Ask(ls(p), x, lp, mid); else val = Ask(rs(p), x, mid + 1, rp);
return Min(val, Calc(tag(p), x));
}
void dfs(int now, int father)
{
Size[now] = 1;
for (int i = Head[now]; i; i = Edge[i].Next)
{
int u = Edge[i].To; if (u == father) continue ;
dfs(u, now); Size[now] += Size[u];
Merge(Root[now], Root[u], LEFT, RIGHT);
}
if (Size[now] == 1)
{
Line[++cntline] = (LINE){b[now], 0}; f[now] = 0;
Insert(Root[now], cntline, LEFT, RIGHT);
}
else
{
f[now] = Ask(Root[now], a[now], LEFT, RIGHT);
Line[++cntline] = (LINE){b[now], f[now]};
Insert(Root[now], cntline, LEFT, RIGHT);
}
}
int main()
{
n = Read(); for (int i = 1; i <= n; ++i) a[i] = Read(); for (int i = 1; i <= n; ++i) b[i] = Read();
for (int i = 1; i < n; ++i) { int x = Read(), y = Read(); addEdge(x, y); addEdge(y, x); }
dfs(1, 1); for (int i = 1; i <= n; ++i) printf("%lld%c", f[i], " \n"[i == n]); return 0;
}