实际上是一个不完美算法……cogs上面A不掉(爆栈啦)。感谢机房大佬PPY的指点,现在也写出来供大家参考参考,理解起来应该是比较简单的一种。
我们首先get出斜率优化方程:
(dp[v] = dis[v] * p[u] - dis[u] * p[u] - q[u] + dp[u] left ( 0 <= dis[u] - dis[v] <= lim[u] ight ))
那么 (y = dp[v]; x = dis[v]; k = p[u]; ) 我们所要做的就是维护一个下凸包以维护最小的b值。现在有两个本题的难点:首先,转移到(u)点的(v)点必须是u的祖先,且两者之间相距的距离在一定的限制范围之内。如何满足?我们想到,既然需要维护一定范围内的一个凸包,那么我们可以借助数据结构来完成。线段树可以做到:一个线段树上的节点代表从(l ~ r)这段区间内的节点所构成的凸包,那么任何一个区间都可以由线段树上上的节点凑成,取每一个中间的最大值即可。
所以我们的线段树所维护的即为一条链上的节点(按深度递增编号)。但还有一个问题没有解决:当我们将一个节点加入凸包的时候我们会为了维护凸包的凸性而弹出一些节点(包括在这个节点的子树都处理完毕之后,该节点自身也要离开凸包,因为它不再属于链上的节点)。那么在处理完之后,我们就希望能够将线段树还原回访问它之前的样子——先想想普通的维护做法:从单调队列的队尾不断弹出元素,直到凸包合法为止。仔细考虑这个过程,我们可以将操作简化为:找到最后一个弹出的元素(a), 意思就是弹出元素(a)后加入当前元素(b)(用(b)替换(a)),再更新队尾指针的指向。所以我们只需要对于每一个节点都维护一下它所弹出的节点&修改的队尾位置就可以了,最后再还原回去。
以下代码——注意INF大大大大大……就这里调了我三个小时,直到我发现了修改INF读到的值竟然不一样的惊天大秘密……Σ(っ°Д°;)っ
#include <bits/stdc++.h> using namespace std; #define maxn 200050 #define lgmaxn 25 #define INF 99999999999999999LL #define ll long long #define db double int n, t; ll P[maxn], Q[maxn], dp[maxn], dis[maxn], len[maxn]; ll X[maxn], Y[maxn], last = 0; int q[maxn * 50], dep[maxn], degree[maxn]; int cnp = 1, head[maxn], maxx; struct tree { int head, tail; }T[maxn * 20]; struct edge { int to, last, co; }E[maxn]; struct node { int pret, id; }S[maxn][lgmaxn]; void add(int u, int v, int w) { E[cnp].to = v, E[cnp].last = head[u]; E[cnp].co = w, head[u] = cnp ++; } db Get_S(int a, int b) { ll x1 = X[a], y1 = Y[a], x2 = X[b], y2 = Y[b]; return (db) (y1 - y2) / (db) (x1 - x2); } void Build(int p, int l, int r) { int size = (r - l + 1); T[p].head = last + 1, T[p].tail = last; last += size; if(l == r) return; int mid = (l + r) >> 1; Build(p << 1, l, mid); Build(p << 1 | 1, mid + 1, r); } void update(int p, int l, int r, int k, int tot, node *a) { int h = T[p].head, &t = T[p].tail, pret = T[p].tail; h += 1; while(h <= t) { int mid = (h + t) >> 1; if(Get_S(q[mid - 1], q[mid]) >= Get_S(q[mid], k)) t = mid - 1; else h = mid + 1; } t ++; a[++ tot] = (node) { pret, q[t] }; q[t] = k; if(l == r) return; int mid = (l + r) >> 1; if(k <= mid) update(p << 1, l, mid, k, tot, a); else update(p << 1 | 1, mid + 1, r, k, tot, a); } int check(int h, int t, int k) { int l = h, r = t - 1, ans = -1; if(l <= r + 1) ans = l; while(l <= r) { int mid = (l + r) >> 1; if(Get_S(q[mid], q[mid + 1]) <= (db) k) ans = mid + 1, l = mid + 1; else r = mid - 1; } return q[ans]; } ll query(int p, ll lim, ll K) { int t = T[p].tail, h = T[p].head; if(dis[q[t]] < lim) return INF; if(dis[q[h]] >= INF) return INF; if(dis[q[h]] >= lim && dis[q[t]] < INF) { ll x = check(T[p].head, T[p].tail, K); x = Y[x] - K * X[x]; return x; } else return min(query(p << 1, lim, K), query(p << 1 | 1, lim, K)); } void rebound(int p, int l, int r, int k, int tot, node *a) { q[T[p].tail] = a[tot].id; T[p].tail = a[tot].pret; int mid = (r + l) >> 1; if(l == r) return; if(k <= mid) rebound(p << 1, l, mid, k, ++ tot, a); else rebound(p << 1 | 1, mid + 1, r, k, ++ tot, a); } void DFS(int u, int fa, ll co) { dep[u] = dep[fa] + 1; ll tem = dis[dep[u] - 1] + co; if(u != 1) dp[u] = query((ll) 1, max((ll) 0, tem - len[u]), P[u]) + tem * P[u] + Q[u]; else dp[u] = 0; if(degree[u]) { dis[dep[u]] = u == 1 ? 0 : tem; X[dep[u]] = dis[dep[u]], Y[dep[u]] = dp[u]; update(1, 1, n, dep[u], 0, S[dep[u]]); for(int i = head[u]; i; i = E[i].last) { int v = E[i].to; DFS(v, u, E[i].co); } rebound(1, 1, n, dep[u], 1, S[dep[u]]); X[dep[u]] = Y[dep[u]] = dis[dep[u]] = INF; } } int main() { scanf("%d%d", &n, &t); for(int i = 0; i <= n; i ++) X[i] = INF, Y[i] = INF, dis[i] = INF; for(int i = 2; i <= n; i ++) { int f, x; scanf("%d%d", &f, &x); scanf("%lld%lld%lld", &P[i], &Q[i], &len[i]); add(f, i, x); degree[f] ++; } Build(1, 1, n); DFS(1, 0, 0); for(int i = 2; i <= n; i ++) printf("%lld ", dp[i]); return 0; }