显然有转移方程:dp[u] = min(dp[f] + p[u] * (dep[u] - dep[f]) + q[u])
,可以用斜率优化,维护下凸包,由于 斜率 p[u] 没有单调性,维护凸包后要二分查找答案。
但这题还有一个距离限制 l [ u ] l[u] l[u], l [ u ] l[u] l[u] 同样不满足单调性,不能直接对 u u u 的所有父亲维护下凸包再二分,这样可能会因为一个在距离外的点而将 u u u 的最优转移点弹出单调栈 ,只能对 u u u 能够到的点维护下凸包。
考虑 CDQ 分治的思想,如果 u u u 节点的父亲那棵子树的所有 d p dp dp 值都已经计算完,将 u u u 的子树节点按它们的能够到的范围排序使得距离限制满足单调性,统计 u u u 的祖先对 u u u 的子树的贡献,再对 u u u 的逐棵子树分治递归计算。
在树是一条链的情况,很容易用 CDQ 分治实现。对比较正常的树,用点分治来实现这个过程。
分治过程:对当前子树寻找一个重心
r
o
o
t
root
root,优先递归计算在原树上 root 的父亲节点所在的子树 v
,对
r
o
o
t
root
root 的其它子树的节点,先计算
v
v
v 子树对这些节点的贡献,然后再递归解决这些子树的问题。
由于这题数据范围特别大,计算斜率移项乘法可能会溢出,要用 double 类型写除法。
复杂度为 O ( n log 2 n ) O(n\log^2n) O(nlog2n),常数不大
#include<bits/stdc++.h>
using namespace std;
#define pii pair<int,ll>
#define fir first
#define sec second
typedef long long ll;
const int maxn = 2e5 + 10;
const ll inf = 1e18;
vector<pii> g[maxn];
int n,t;
int sta[maxn],top;
ll dp[maxn],q[maxn],step[maxn],p[maxn],dep[maxn],s[maxn],d[maxn],mi[maxn];
int fa[maxn],vis[maxn],siz[maxn],root,cnt,F[maxn];
int tot;
struct node {
ll dis,v; //v 代表点,dis 代表可以向上延伸的最大距离
node() {}
node(ll d,ll vi) {
dis = d; v = vi;
}
bool operator < (const node &rhs) const { //按向上的范围从小到大排序
return dis < rhs.dis;
}
}pot[maxn];
ll getup(int x,int y) {
return dp[x] - dp[y];
}
ll getdown(int x,int y) {
return step[x] - step[y];
}
ll calc(int x,int y) {
return dp[y] + 1ll * p[x] * (dep[x] + step[y]) + q[x];
}
double K(int x,int y) {
if (!y) return -1e18;
return (dp[x] - dp[y]) / (double) (step[x] == step[y] ? 1e-5 : step[x] - step[y]);
}
void solve(int u) { //对 u 进行求解
int l = 1,r = top + 1;
while (l < r) { //二分找到最后一个点,满足上一个点比它优
int mid = l + r >> 1;
if (K(sta[mid],sta[mid - 1]) > -1 * p[u]) r = mid;
else l = mid + 1;
}
if (l > 1)
dp[u] = min(dp[u],calc(u,sta[l - 1]));
}
void getroot(int u,int p) {
siz[u] = 1, F[u] = 0;
for (auto it : g[u]) {
int v = it.fir;
if (vis[v] || v == p) continue;
getroot(v,u);
siz[u] += siz[v];
F[u] = max(F[u],siz[v]);
}
F[u] = max(F[u],cnt - siz[u]);
if (!root || F[root] > F[u])
root = u;
}
void dfs(int u,int p) {
pot[++tot] = node(d[u] - dep[u],u);
for (auto it : g[u]) {
int v = it.fir;
if (v == p || vis[v]) continue;
dep[v] = dep[u] + it.sec;
dfs(v,u);
}
}
void insert(int x) {
while (top > 1 && K(x,sta[top]) <= K(sta[top],sta[top - 1]))
top--;
sta[++top] = x;
}
void divide(int u) { //解决以 u 的父节点到根的链对 以 u为根的子树问题
root = 0; getroot(u,0); vis[root] = 1;
int rt = root, v = rt;
if (u != rt) {
cnt = cnt - siz[rt];
divide(u);
}
step[rt] = dep[rt] = 0; tot = top = 0; dfs(rt,0);
sort(pot + 1,pot + tot + 1);
for (int i = 1; i <= tot; i++) {
while (v != fa[u] && pot[i].dis >= step[v] + s[v] && fa[v]) {
step[fa[v]] = step[v] + s[v];
insert(fa[v]); v = fa[v];
}
if (top) solve(pot[i].v);
}
for (auto it : g[rt]) {
int v = it.fir;
if (!vis[v]) {
cnt = siz[v];
divide(v);
}
}
}
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],&d[i]);
g[fa[i]].push_back(pii(i,s[i]));
g[i].push_back(pii(fa[i],s[i]));
dp[i] = inf;
}
vis[0] = 1; cnt = n; divide(1);
for (int i = 2; i <= n; i++) {
printf("%lld\n",dp[i]);
}
return 0;
}