题意
给定一棵边带权且以 1 为根的树,从后代结点 u 跳到祖先结点 v 的代价为 dpu+qu,其中 pu,qu 是给定的常数,d 是 u,v 的树上距离。要求只有 d≤lu 时才能从 u 跳到 v。∀1<i≤n,求从点 i 跳到点 1 的最小代价。
n≤2×105
思路
点分治 + 斜率优化 dp.
观察转移的代价代价,可以写成 (depth(u)−depth(v))pu+qu+fv 的形式,拆开得到:
depth(u)pu−depth(v)pu+qu
满足斜率优化的形式,考虑从点 i 转移优于从点 j 转移的条件:
depth(u)pu−depth(i)pu+qu+fi≤depth(u)pu−depth(j)pu+qu+fj
即 fi−depth(i)pu≤fj−depth(j)pu
移项得到 fi−fj≤depth(i)pu−depth(j)pu
即 fi−fjdepth(i)−depth(j)≤pu
但是这里的斜率优化是在树上做的,随 dfs 回溯复杂度摊下来是假的。
于是可以考虑用点分治优化。
点分治考虑的是每次划分出子树重心,那么转移的贡献可以分成两部分:
-
当前子树除重心所在子树之外的部分 -> 其自身
-
当前子树除重心所在子树之外的部分 -> 重心所在子树
那么可以考虑在点分治的时候维护第二类贡献。
但是问题在于每个点可以转移到范围不固定也不单调,比较难搞。
这里可以考虑在点分每层的时候把所有结点按照可以转移的范围从小到大排序,于是就不需要考虑撤销操作。
那么现在只需要考虑维护加入点的贡献即可,这里直接上果的丹钓战。
注意 x 坐标不单调,要在凸包上二分最优的转移位置。
代码
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn = 2e5 + 5;
const ll inf = 1e18;
struct Point
{
ll x, y;
Point() : x(), y() {}
Point(ll _x, ll _y) : x(_x), y(_y) {}
Point operator - (Point rhs) { return Point(x - rhs.x, y - rhs.y); }
} stk1[maxn], stk2[maxn];
int n, t;
int top1, top2, rt, tot;
int fa[maxn], mx[maxn], sz[maxn];
ll d[maxn], dp[maxn];
ll s[maxn], l[maxn], p[maxn], q[maxn];
bool vis[maxn];
double sl[maxn];
vector<int> g[maxn];
void dfs1(int u)
{
sz[u] = 1, mx[u] = 0;
for (int v : g[u])
{
if (!vis[v])
{
dfs1(v);
sz[u] += sz[v];
mx[u] = max(mx[u], sz[v]);
}
}
if ((rt == -1) || max(mx[u], tot - sz[u]) < max(mx[rt], tot - sz[rt])) rt = u;
}
void dfs2(int u)
{
if (l[u] - d[u] > 0) stk1[++top1] = Point(l[u] - d[u], u);
for (int v : g[u])
if (!vis[v]) d[v] = d[u] + s[v], dfs2(v);
}
bool cmp(Point a, Point b) { return (a.x < b.x); }
double slope(Point a, Point b) { return (double)(a.y - b.y) / (a.x - b.x); }
void insert(Point a)
{
while ((top2 > 1) && (slope(a, stk2[top2]) <= sl[top2])) top2--;
stk2[++top2] = a;
sl[top2] = (top2 > 1 ? slope(stk2[top2], stk2[top2 - 1]) : -inf);
}
ll query(ll k)
{
int l = 1, r = top2;
ll res = 0;
while (l <= r)
{
int mid = (l + r) >> 1;
if (sl[mid] <= k) res = stk2[mid].y - stk2[mid].x * k, l = mid + 1;
else r = mid - 1;
}
return res;
}
void solve(int u)
{
rt = -1;
dfs1(u);
vis[rt] = true;
int nd = rt;
if (nd != u) tot -= sz[nd], solve(u);
int v = nd;
ll dis = 0;
top1 = top2 = d[nd] = 0, dfs2(nd);
sort(stk1 + 1, stk1 + top1 + 1, cmp);
for (int i = 1; i <= top1; i++)
{
ll lim = stk1[i].x;
int w = stk1[i].y;
while ((v != fa[u]) && (dis + s[v] <= lim) && fa[v])
{
dis += s[v];
insert(Point(dis, dp[fa[v]]));
v = fa[v];
}
if (top2) dp[w] = min(dp[w], query(-p[w]) + d[w] * p[w] + q[w]);
}
for (int to : g[nd])
if (!vis[to]) tot = sz[to], solve(to);
}
int main()
{
scanf("%d%d", &n, &t);
for (int i = 2; i <= n; i++)
{
scanf("%d%lld%lld%lld%lld", &fa[i], &s[i], &p[i], &q[i], &l[i]);
g[fa[i]].push_back(i);
dp[i] = inf;
}
tot = n;
vis[0] = true;
solve(1);
for (int i = 2; i <= n; i++) printf("%lld\n", dp[i]);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现