[NOI 2014] 购票

一棵以1为根的n个结点的树, 边带权 (视为距离). 从某点u到达树根的方式: 支付一定代价跳到距离不超过l[u]的祖先v, 再支付一定代价跳到v的某个距离不超过l[v]的祖先w......设一次跳跃的起点为s, 起点终点之间的距离为d, 则这次跳跃的代价为(p[s]d+q[s]), p, q是给定的参数. 求每个点到达树根的最小代价. (0≤p≤10^6, 0≤q≤10^12, 1≤fa[v]<v, 0<v与父亲的距离≤l[v]≤2*10^11, n≤2*10^5) 设点的最小代价为, 那么 Missing \left or extra \right f[v] = \min_{\text{u is anc of u }d(u,v)\le l[v]}\left\\{f[u] + d(u,v)p[v] + q[v]\right\\}

一个关键而常用 (但我却没想到) 的处理: Missing \left or extra \right f[v] = \min_{\text{u is anc of u }d[u]\ge d[v]-l[v]}\left\\{f[u]-p[v]d[u]\right\\} + q[v] + p[v]d[v]

花括号内的表达式具有斜率优化的形式. 维护的下凸壳.

但现在在树上, 又有距离限制......用可持久化Treap替代Splay维护凸壳? 需要对横坐标打标记? (那时没意识到可以作距离的前缀和) 空间开得下吗?

去瞥了一眼题解, 线段树? 树链剖分?

突然领悟到距离限制会使得凸壳的形态改变......Too young, too simple...Po姐的题解里有一张图, 说明了这种情况.

以为线段树维护凸壳不好写, 可不可以分治呢? CDQ分治? 这是序列......点分治? 这是有根树, 不太好处理吧......

也许可以结合一下, 先点分治, 然后像CDQ分治一样考虑包含根的那棵子树对其他子树的影响......但是没多思考就看题解了, 还真是这样 QAQ 失去一个独立发现正解的机会......思维不要局限啊......可以直接上经典算法它就没必要出到NOI了......

先对包含根的那棵子树递归分治, 然后, 包含根的那棵子树+重心对其他子树的影响这样计算: 将其他子树的结点取出来, 按照从大到小排序, 将重心->根上的结点按需加入凸壳 (单调栈维护), 查询则在凸壳上二分 (可以用lower_bound). 重心必须加入凸壳, 否则永远不会计算它对子树的影响; 可以将它和包含根的子树一起分治 (注意n=2时避免死循环), 也可以暴力计算 (不要像我一样忘了距离限制 QAQ). 最后, 其他子树递归.

树链剖分多一个log, 但代码长度可以做到以下的一半......果然树链剖分通常跑不满.

typedef long long ll;
typedef long double ld;
typedef pair<int, int> ii;
typedef pair<ll, int> li;

const int N = 2e5;
const ld Zero = 0, inf = 1/Zero;

struct Point {
	ll x, y;
	Point(ll x=0, ll y=0): x(x), y(y) {}
	Point operator-(const Point& o) const
	{
		return Point(x-o.x, y-o.y);
	}
	friend ld slope(const Point& a, const Point& b)
	{
		return (ld)(a.y - b.y) / (a.x - b.x);
	}
};

struct Convex_Hull {
	Point S[N];
	ld k[N];
	int top;
	Convex_Hull(): top(0) {}

	void add(ll x, ll y)
	{
		Point p(x, y);
		while (top > 1 && slope(p, S[top-1]) >= k[top-2]) --top;
		k[top] = -inf;
		if (top) k[top-1] = slope(p, S[top-1]);
		S[top++] = p;
	}

	Point query(ld s)
	{
		return S[lower_bound(k, k+top, s, greater<ld>()) - k];
	}

	void clear()
	{
		top = 0;
	}
} C;

vector<int> adj[N + 1];
vector<li> seq;
int fa[N + 1], sz[N + 1];
ll f[N + 1], s[N + 1], p[N + 1], q[N + 1], l[N + 1], d[N + 1];
bool g[N + 1];

int get_centroid(int u, const int& n)
{
	int mx = 0, c = 0;
	sz[u] = 1;
	Rep (i, 0, adj[u].size()) {
		int v = adj[u][i], ret;
		if (g[v]) continue;
		if ((ret = get_centroid(v, n))) c = ret;
		mx = max(mx, sz[v]);
		sz[u] += sz[v];
	}
	mx = max(mx, n-sz[u]);
	return c ? c : (mx <= n/2 ? u : 0);
}

void dfs(int u)
{
	Rep (i, 0, adj[u].size()) {
		int v = adj[u][i];
		if (!g[v]) {
			seq.push_back(li(d[v] - l[v], v));
			dfs(v);
		}
	}
}

void solve(int u, const int& n)
{
	int x = get_centroid(u, n);
	g[x] = true;
	if (x != u) {
		solve(u, n-sz[x]);
		int y = x;
		do {
			y = fa[y];
			if (d[x] - d[y] > l[x]) break;
			f[x] = min(f[x], f[y] + (d[x]-d[y]) * p[x] + q[x]);
		} while (y != u);
	}
	
	dfs(x);
	sort(seq.begin(), seq.end(), greater<li>());

	int z = x;
	
	Rep (i, 0, seq.size()) {
		int y = seq[i].second;
		while (z && d[z] >= seq[i].first) {
			C.add(d[z], f[z]);
			z = z == u ? 0 : fa[z];
		}
		if (C.top) {
			Point t = C.query(p[y]);
			f[y] = min(f[y], t.y + p[y] * (d[y] - t.x) + q[y]);
		}
	}

	C.clear();
	seq.clear();

	Rep (i, 0, adj[x].size()) {
		int y = adj[x][i];
		if (!g[y]) solve(y, sz[y]);
	}
}

void cal_d(int u)
{
	Rep (i, 0, adj[u].size()) {
		int v = adj[u][i];
		d[v] = d[u] + s[v];
		cal_d(v);
	}
}

int main()
{
	int n, t;
	scanf("%d%d", &n, &t);
	For (i, 2, n) {
		scanf("%d%lld%lld%lld%lld", &fa[i], &s[i], &p[i], &q[i], &l[i]);
		adj[fa[i]].push_back(i);
	}
	cal_d(1);
	fill_n(f+2, n-1, numeric_limits<ll>::max());
	solve(1, n);
	For (i, 2, n) printf("%lld\n", f[i]);
	return 0;
}