Codeforces Round #463 F. Escape Through Leaf (李超线段树合并)

听说正解是啥 set启发式合并+维护凸包+二分 根本不会啊 , 只会 李超线段树合并 啦 ...

题意

给你一颗有 n 个点的树 , 每个节点有两个权值 ai,bi .

u 跳到 v 的代价是 au×bv . 你需要计算每个节点跳到叶子的最小代价 .

(n105,105ai,bi105)

题解

我们首先考虑一个很容易的 dp , 令 dpii 跳到叶子的最小代价 .

那么显然有一个转移 此处 vu 的后代 .

dpu=minv{a[u]×b[v]+dpv}

暴力转移是 O(n2) 的显然无法接受 .

那么考虑优化 , 不难发现这个转移就是 李超线段树上求多条直线 y=kx+bx=k 最值的形式 .

(k=b[v],x=a[u],b=dpv)

那么显然可以考虑用李超线段树维护这个 dp .

对于树上的每个点 , 可以用一颗李超线段树维护这个点子树的所有直线信息 .

然后我们只需要考虑合并几颗子树信息了 , 不难发现是 套路的 线段树合并 . (这样时间空间复杂度都正确了?)

我们直接同时遍历两颗线段树 , 然后把其中一颗当前区间的优势直线暴力插入另外一颗线段树 .

最后把子树全都合并上来后 , 直接询问出 dpu 就行了 , 然后再插入到这颗线段树中去 .

由于询问 ai 可能为负数 , 而线段树不太好维护负数 , 我们考虑插入直线和询问的时候都向右平移 lim=105 长度 .

原来的直线 y=kx+b 就变成了 y=k(xlim)+b=kx+(bklim) 了 .

(也许可以维护负数 diversion 说可以)

最后瞎分析时间复杂度 O(minsizelogn)=O(nlog2n) ... (错了的话大佬帮我指正啊qwq)

这道题还是比较好写的 ...

代码

#include <bits/stdc++.h> #define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i) #define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i) #define Set(a, v) memset(a, v, sizeof(a)) #define debug(x) cout << #x << ':' << x << endl using namespace std; inline bool chkmin(int &a, int b) {return b < a ? a = b, 1 : 0;} inline bool chkmax(int &a, int b) {return b > a ? a = b, 1 : 0;} inline int read() { int x = 0, fh = 1; char ch = getchar(); for (; !isdigit(ch); ch = getchar()) if (ch == '-') fh = -1; for (; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + (ch ^ 48); return x * fh; } void File() { #ifdef zjp_shadow freopen ("F.in", "r", stdin); freopen ("F.out", "w", stdout); #endif } const int N = 2e5 + 1e3, Lim = 1e5 + 5; typedef long long ll; struct Line { ll k, b; int id; ll func(int x) { return k * x + b; } }; inline bool Cmp(Line a, Line b, int x) { if (!a.id) return true; return a.func(x) > b.func(x); } int rt[N]; const int Maxn = N * 30; #define lson ls[o], l, mid #define rson rs[o], mid + 1, r struct Chao_Segment_Tree { Line Adv[Maxn]; int ls[Maxn], rs[Maxn], Size; Chao_Segment_Tree () {Size = 0;}; void Insert(int &o, int l, int r, Line uv) { if (!o) o = ++ Size; int mid = (l + r) >> 1; if (Cmp(Adv[o], uv, mid)) swap(Adv[o], uv); if (l == r || Adv[o].k == uv.k || !uv.id) return ; double x = (double)(Adv[o].b - uv.b) / (uv.k - Adv[o].k); if (x < l || x > r) return ; if (uv.k > Adv[o].k) Insert(lson, uv); else Insert(rson, uv); } int Merge(int x, int y, int l, int r) { if (!x || !y) return x + y; Insert(x, l, r, Adv[y]); int mid = (l + r) >> 1; ls[x] = Merge(ls[x], ls[y], l, mid); rs[x] = Merge(rs[x], rs[y], mid + 1, r); return x; } Line Query(int o, int l, int r, int qp) { if (l == r) return Adv[o]; int mid = (l + r) >> 1; Line tmp = (qp <= mid) ? Query(lson, qp) : Query(rson, qp); return Cmp(tmp, Adv[o], qp) ? Adv[o] : tmp; } } T; int n, A[N], B[N]; vector<int> G[N]; ll dp[N]; inline void Insert(int ver) { ll k = B[ver], b = dp[ver] - Lim * k; T.Insert(rt[ver], 1, Lim * 2, (Line) {k, b, ver}); } inline ll Query(int ver) { int tmp = T.Query(rt[ver], 1, Lim * 2, A[ver] + Lim).id; return 1ll * A[ver] * B[tmp] + dp[tmp]; } void Dp(int u, int fa) { for (int v : G[u]) if (v ^ fa) Dp(v, u), rt[u] = T.Merge(rt[u], rt[v], 1, Lim * 2); dp[u] = Query(u); Insert(u); } int main () { File(); n = read(); For (i, 1, n) A[i] = read(); For (i, 1, n) B[i] = read(); For (i, 1, n - 1) { int u = read(), v = read(); G[u].push_back(v); G[v].push_back(u); } Dp(1, 0); For (i, 1, n) printf ("%lld%c", dp[i], i == iend ? '\n' : ' '); return 0; }

__EOF__

本文作者zjp_shadow
本文链接https://www.cnblogs.com/zjp-shadow/p/9180474.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   zjp_shadow  阅读(728)  评论(2编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示