洛谷 P2305 购票 —— 斜率dp + 点分治

题目链接:点我啊╭(╯^╰)╮

题目大意:

    从城市 i i i 到城市 j j j 的代价为 d ∗ p i + q i d*p_i+q_i dpi+qi
     d d d 为距离,且 j j j i i i 的祖先节点
    每个城市有一个限制 l i l_i li,表示一次购票距离的上限
    求每个城市到根节点的最少代价

解题思路:

     d p [ i ] = m i n ( d p [ j ] + ( d i s i − d i s j ) ∗ p i + q i ) dp[i] = min(dp[j] + (dis_i - dis_j) * p_i + q_i) dp[i]=min(dp[j]+(disidisj)pi+qi)
     j j j i i i 的祖先节点, d i s dis dis 为到根节点的距离
    很明显具有斜率优化的性质,维护上凸包, p i p_i pi 不单调,需要二分

    关键在于这是一棵树,需要用点分治处理
在这里插入图片描述
    第三步按照 d i s i − l i dis_i - l_i disili 排序,大的排在前面,因为要淘汰
    将祖先节点放在 A A A 里,排序后的子节点放在 B B B
    对于 B B B 而言,答案就可以在 A A A 里斜率 d p dp dp + + + 二分处理

    注意枚举 B B B 的时候,要确保可能的 A A A 都已经遍历到了
    最后二分结果要 + 1 +1 +1,才是最优节点,并且队尾的斜率为 − ∞ -∞
    找重心有一个特殊情况:两个点的重心必须是父亲节点!

#include<bits/stdc++.h>
#define rint register int
#define deb(x) cerr<<#x<<" = "<<(x)<<'\n';
using namespace std;
typedef long long ll;
typedef pair <int,int> pii;
const int maxn = 2e5 + 5;
int n, m, t, f[maxn], hvy, mxp, sz[maxn];
int rt, an, bn, A[maxn], B[maxn], vis[maxn], Q[maxn];
ll p[maxn], q[maxn], l[maxn], dp[maxn], dis[maxn], s[maxn];
vector <int> g[maxn];
double sl[maxn];

void getdis(int u){
	for(auto v : g[u]){
		dis[v] = dis[u] + s[v];
		getdis(v);
	}
}

void gethvy(int u){
	sz[u] = 1; int tmxp = 0;
	for(auto v : g[u]){
		if(vis[v]) continue;
		gethvy(v);
		sz[u] += sz[v];
		tmxp = max(tmxp, sz[v]);
	}
	tmxp = max(tmxp, m - sz[u]);
	if(tmxp <= mxp){	// 注意少了 = 会死循环 
		mxp = tmxp;
		hvy = u;
	}
}

void dfs(int u){
	B[++bn] = u;
	for(auto v : g[u]){
		if(vis[v]) continue;
		dfs(v);
	}
}

bool cmp(int ca, int cb){
	return dis[ca] - l[ca] > dis[cb] - l[cb];
}

double slope(int i, int j){
	return 1.0 * (dp[i] - dp[j]) / (dis[i] - dis[j]);
}

void solve(int H){
	if(m <= 1) return;
	mxp = m, hvy = H;
	gethvy(H); int nhvy = hvy;
	for(auto v : g[hvy]) vis[v] = 1, m -= sz[v];
	solve(H); hvy = nhvy;
	
	rt = hvy, an = 0, bn = 0;
	while(rt ^ H) A[++an] = rt, rt = f[rt];
	A[++an] = H;
	for(auto v : g[hvy]) dfs(v);
	sort(B+1, B+1+bn, cmp);
	
	int tail = 0, bi = 1;
	while(bi<=bn && dis[B[bi]]-l[B[bi]]>dis[hvy]) bi++;
	
	for(int i=1; i<=an; i++){
		while(tail>1 && sl[tail-1]<=slope(Q[tail], A[i])) tail--;
		Q[++tail] = A[i], sl[tail] = -1e18, sl[tail-1] = slope(Q[tail-1], Q[tail]);
		while(bi<=bn && (i==an || dis[B[bi]]-l[B[bi]]>dis[A[i+1]])){
			int L = 1, R = tail, mid;
			while(L <= R){
				mid = L + R >> 1;
				if(sl[mid] >= p[B[bi]]) L = mid + 1;
				else R = mid - 1;
			}
			int ti = B[bi], tj = Q[R+1]; // R + 1 == L
			dp[ti] = min(dp[ti], dp[tj] + p[ti] * (dis[ti] - dis[tj]) + q[ti]);
			bi++;
		}
	}
	for(auto v : g[hvy]) m = sz[v], solve(v);
}

signed main() {
	scanf("%d%d", &n, &t);
	for(int i=2; i<=n; i++){
		scanf("%d%lld%lld%lld%lld", f+i, s+i, p+i, q+i, l+i);
		g[f[i]].push_back(i);
		dp[i] = 1e18;
	}
	getdis(1);
	m = n;
	solve(1);
	for(int i=2; i<=n; i++) printf("%lld\n", dp[i]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值