P2305 [NOI2014]购票(点分治 + CDQ分治 + 斜率优化)

在这里插入图片描述


显然有转移方程: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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值