NOI2014购票
2022/2/15 23:13:12
本文主要是介绍NOI2014购票,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
题意:
给出根节点为 \(1\) 的一颗树,\(d_i\) 表示 \(1\) 到 \(i\) 的距离, 每个点 \(i\) 可以跳到距离 \(\leq l_i\) 的点 \(j\) 上,花费是 \((d_i - d_j) \times p_i + q_i\),求每个点到根节点的最小花费。
dp 方程转移:
\[f_i = \min \{f_j + (d_i - d_j) \times p_i + q_i\} \]斜率优化 —— dp 优化
将各项分离,就是可以斜率优化的式子:
\[f_i = \min \{f_j - d_j \times p_i \} + d_i \times p_i + q_i \]形式化一下:
\[\left \{ \begin{aligned} Y &= f_j \\ X &= d_j \\ K &= p_i \\ D &= d_i \times p_i + q_i \end{aligned} \right. \]求最小截距 \(B = Y_j - X_j \times K_i + D_i\)。
但是建立可转移点的凸包并不简单,如果插入的 \((X, Y)\) 不保证 \(X\) 的单调性,那么就要用平衡树维护凸包,代码难度爆炸。
点分治 —— 处理树上路径问题
- 在一条链上的 \(d_i\) 是单调的。
利用点分治,对于每个重心,优先处理重心到根节点的子树,先算出它们的 \(f\) 值。
这时再把重心剩下子树的所有节点按照 能到达的深度 大到小排序,依次对于每个结点更新 \(f\) 值,这时分治重心到根的点会依次加入凸包,由于 \(d_i\) 单调递减,
维护一个下凸壳,它的斜率是单调递减的, 可用单调栈维护。
对于每个结点最优决策点,单调栈中的点是它所能到达的所有祖先结点 ,直接用 \(K_i\) 在单调栈二分即可。
代码
#include<bits/stdc++.h> using namespace std; using ll = long long; const int MAXN = 200010; const ll INF = 1e18; // 大点 const int mod = 1000000007; //#define ll long long template <typename T> void Read(T &x) { x = 0; T f = 1; char a = getchar(); for(; a < '0' || '9' < a; a = getchar()) if (a == '-') f = -f; for(; '0' <= a && a <= '9'; a = getchar()) x = (x * 10) + (a ^ 48); x *= f; } int n, t; int fa[MAXN]; ll q[MAXN], p[MAXN], l[MAXN], dis[MAXN], w[MAXN]; vector<int> e[MAXN]; void getdis(int u) { for (int v : e[u]) { dis[v] = dis[u] + w[v]; getdis(v); } } ll ma, S, root; ll siz[MAXN], mx[MAXN]; bool vis[MAXN]; void getroot(int u) { siz[u] = 1; mx[u] = 0; for (int v : e[u]) { if (vis[v]) continue; getroot(v); siz[u] += siz[v]; mx[u] = max(mx[u], siz[v]); } mx[u] = max(mx[u], S - siz[u]); if (mx[u] <= ma) ma = mx[u], root = u; // 只有两个点时,如果找到的重心是 vis 被标 1 的点, 那么它的 siz 就是错的,就会死循环。 } int len; int id[MAXN]; void dfs(int u) { id[++ len] = u; for (int v : e[u]) { if (!vis[v]) dfs(v); } } /* f[i] = f[j] + (d[i] - d[j]) * p[i] + q[i] f[i] = f[j] + d[i]p[i] - d[j]*p[i] + q[i] Y = f[j] X = d[j] K = p[i] D = q[i] + d[i]p[i] */ ll f[MAXN]; ll K(int i) { return p[i]; } ll X(int j) { return dis[j]; } ll Y(int j) { return f[j]; } ll D(int i) { return q[i] + dis[i] * p[i]; } double slope(int i, int j) { return 1.0 * (Y(i) - Y(j)) / (X(i) - X(j)); } int top, s[MAXN]; void insert(int x) { while(top > 1 && slope(s[top - 1], s[top]) <= slope(s[top], x)) top --; s[++ top] = x; } ll query(int u) { int l = 0, r = top; while (l + 1 < r) { int mid = (l + r) >> 1; if (slope(s[mid], s[mid + 1]) <= K(u)) r = mid; else l = mid; } return s[r]; } void solve(int u, int Siz) { if (Siz == 1) return ; S = Siz, ma = INF, root = - 1; getroot(u); int rt = root; for (int v : e[rt]) { Siz -= siz[v]; vis[v] = 1; } solve(u, Siz); len = 0; for (int v : e[rt]) dfs(v); sort(id + 1, id + len + 1, [=](int x, int y) { return dis[x] - l[x] > dis[y] - l[y]; }); int now = rt; top = 0; for (int j = 1; j <= len; j ++) { int i = id[j]; while(now != fa[u] && dis[now] >= dis[i] - l[i]) insert(now), now = fa[now]; if (top) { int t = query(i); f[i] = min(f[i], Y(t) - K(i) * X(t) + D(i)); } } for (int v : e[rt]) solve(v, siz[v]); } int main() { Read(n); Read(t); for (int i = 2; i <= n; i ++) { Read(fa[i]), Read(w[i]), Read(p[i]), Read(q[i]), Read(l[i]); e[fa[i]].emplace_back(i); f[i] = INF; } getdis(1); solve(1, n); for (int i = 2; i <= n; i ++) printf("%lld\n", f[i]); return 0; }
这篇关于NOI2014购票的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-12-23线下车企门店如何实现线上线下融合?
- 2024-12-23鸿蒙Next ArkTS编程规范总结
- 2024-12-23物流团队冬至高效运转,哪款办公软件可助力风险评估?
- 2024-12-23优化库存,提升效率:医药企业如何借助看板软件实现仓库智能化
- 2024-12-23项目管理零负担!轻量化看板工具如何助力团队协作
- 2024-12-23电商活动复盘,为何是团队成长的核心环节?
- 2024-12-23鸿蒙Next ArkTS高性能编程实战
- 2024-12-23数据驱动:电商复盘从基础到进阶!
- 2024-12-23从数据到客户:跨境电商如何通过销售跟踪工具提升营销精准度?
- 2024-12-23汽车4S店运营效率提升的核心工具