题意
给定一棵边带权且以 \(1\) 为根的树,从后代结点 \(u\) 跳到祖先结点 \(v\) 的代价为 \(dp_u + q_u\),其中 \(p_u, q_u\) 是给定的常数,\(d\) 是 \(u, v\) 的树上距离。要求只有 \(d \leq l_u\) 时才能从 \(u\) 跳到 \(v\)。\(\forall 1 < i \leq n\),求从点 \(i\) 跳到点 \(1\) 的最小代价。
\(n \leq 2 \times 10^5\)
思路
点分治 + 斜率优化 dp.
观察转移的代价代价,可以写成 \((\operatorname{depth}(u) - \operatorname{depth}(v))p_u + q_u + f_v\) 的形式,拆开得到:
\(\operatorname{depth}(u) p_u - \operatorname{depth}(v)p_u + q_u\)
满足斜率优化的形式,考虑从点 \(i\) 转移优于从点 \(j\) 转移的条件:
\(\operatorname{depth}(u) p_u - \operatorname{depth}(i) p_u + q_u + f_i \leq \operatorname{depth}(u) p_u - \operatorname{depth}(j)p_u + q_u + f_j\)
即 \(f_i - \operatorname{depth}(i) p_u \leq f_j - \operatorname{depth}(j)p_u\)
移项得到 \(f_i - f_j \leq \operatorname{depth}(i) p_u - \operatorname{depth}(j)p_u\)
即 \(\frac{f_i - f_j}{\operatorname{depth}(i) - \operatorname{depth}(j)} \leq p_u\)
但是这里的斜率优化是在树上做的,随 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;
}
标签:Point,int,题解,ll,operatorname,NOI2014,depth,maxn,P2305
From: https://www.cnblogs.com/lingspace/p/p2305.html