bzoj 3672 [Noi2014]购票 斜率优化+cdq分治+点分治

题面

题目传送门

解法

  • 先不考虑树的情况,考虑一条链怎么做。
  • 显然可以写出dp: f [ i ] = m i n ( f [ j ] + ( d [ i ] − d [ j ] ) p [ i ] + q [ i ] ) f[i]=min(f[j]+(d[i]-d[j])p[i]+q[i]) f[i]=min(f[j]+(d[i]d[j])p[i]+q[i])。然后把式子稍作展开,可以发现这显然是一个斜率优化的形式。决策的点坐标为 ( d [ j ] , f [ j ] ) (d[j],f[j]) (d[j],f[j]),斜率为 p [ i ] p[i] p[i]。因为斜率并不单调,所以我们需要用cdq分治维护一个下凸壳来实现这一过程。
  • 然后考虑一棵树的情况怎么实现。
  • 大概有两种方法:一种是维护一个可持久化的单调队列来实现动态维护下凸壳的过程。这个可以用可持久化线段树来实现,但是细节较多。
  • 另一种考虑点分治。假设我们正在处理分治重心为 r t rt rt的子树 x x x,先处理出 r t rt rt子树外的点的 d p dp dp值,然后用cdq分治来处理 x x x r t rt rt的路径上的点对 r t rt rt子树内 d p dp dp值的贡献,最后分治下去即可,比较容易实现。
  • 时间复杂度: O ( n log ⁡ 2 n ) O(n\log^2n) O(nlog2n)

【注意事项】

  • 在cdq分治的过程中,由于从分治重心 r t rt rt到整棵树的根 x x x的路径中深度逐渐递减,所以维护凸包的时候需要稍微注意一下。

代码

#include <bits/stdc++.h>
#define ld long double
#define ll long long
using namespace std;
template <typename T> void chkmax(T &x, T y) {x = x > y ? x : y;}
template <typename T> void chkmin(T &x, T y) {x = x < y ? x : y;}
template <typename T> void read(T &x) {
	x = 0; int f = 1; char c = getchar();
	while (!isdigit(c)) {if (c == '-') f = -1; c = getchar();}
	while (isdigit(c)) x = x * 10 + c - '0', c = getchar(); x *= f;
}
const int N = 200010;
int rt, cnt, tot, f[N], fa[N], st[N], vis[N], siz[N], head[N];
ll d[N], p[N], q[N], dp[N], tx[N], ty[N], lim[N];
struct Node {
	int x; ll v;
	bool operator < (const Node &a) const {return v > a.v;}
} a[N];
struct Edge {int next, num; ll v;} e[N * 3];
ld slope(int i, int j) {
	ld x = ty[i] - ty[j], y = tx[i] - tx[j];
	return x / y;
}
void add(int x, int y, ll v) {e[++cnt] = (Edge) {head[x], y, v}, head[x] = cnt;}
void getdep(int x) {
	for (int p = head[x]; p; p = e[p].next) {
		int k = e[p].num; ll v = e[p].v;
		d[k] = d[x] + v, getdep(k);
	}
}
void getr(int x, int s) {
	siz[x] = 1, f[x] = 0;
	for (int p = head[x]; p; p = e[p].next) {
		int k = e[p].num; if (vis[k]) continue;
		getr(k, s), siz[x] += siz[k];
		chkmax(f[x], siz[k]);
	}
	chkmax(f[x], s - siz[x]);
	if (f[x] < f[rt] && siz[x] > 1) rt = x;
}
void dfs(int x) {
	a[++tot] = (Node) {x, d[x] - lim[x]};
	for (int p = head[x]; p; p = e[p].next) if (!vis[e[p].num]) dfs(e[p].num);
}
ll query(int i, int top) {
	if (!top) return 1ll << 60;
	int l = 2, r = top, j = st[1];
	while (l <= r) {
		int mid = (l + r) >> 1;
		if (slope(st[mid - 1], st[mid]) >= p[i]) j = st[mid], l = mid + 1;
			else r = mid - 1;
	}
	return dp[j] + (d[i] - d[j]) * p[i] + q[i];
}
void solve(int x, int s) {
	if (s == 1) return tx[x] = d[x], ty[x] = dp[x], void();
	f[rt = 0] = s, getr(x, s); int t = rt;
	for (int p = head[rt]; p; p = e[p].next) vis[e[p].num] = 1;
	solve(x, s - siz[rt] + 1); tot = 0;
	for (int p = head[t]; p; p = e[p].next) dfs(e[p].num);
	sort(a + 1, a + tot + 1); int now = t, top = 0;
	for (int i = 1; i <= tot; i++) {
		while (d[now] >= d[x] && a[i].v <= d[now]) {
			while (top > 1 && slope(st[top - 1], st[top]) < slope(st[top], now)) top--;
			st[++top] = now, now = fa[now];
		}
		chkmin(dp[a[i].x], query(a[i].x, top));
	}
	for (int p = head[t]; p; p = e[p].next) solve(e[p].num, siz[e[p].num]);
}
int main() {
	int n, t; read(n), read(t);
	for (int i = 2; i <= n; i++) {
		ll v; read(fa[i]), read(v), add(fa[i], i, v);
		read(p[i]), read(q[i]), read(lim[i]);
	}
	getdep(1); d[0] = -1;
	memset(dp, 0x3f, sizeof(dp)); dp[1] = 0;
	solve(1, n);
	for (int i = 2; i <= n; i++) cout << dp[i] << '\n';
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值