洛谷P3994【比赛】Highway 【斜率优化】

41 篇文章 1 订阅
9 篇文章 0 订阅

题目背景

C国拥有一张四通八达的高速公路树,其中有n个城市,城市之间由一共n-1条高速公路连接。除了首都1号城市,每个城市都有一家本地的客运公司,可以发车前往全国各地,有若干条高速公路连向其他城市,这是一个树型结构,1号城市(首都)为根。假设有一个人要从i号城市坐车出发前往j号城市,那么他要花费Pi*(i城市到j城市的距离)+Qi元。由于距离首都越远,国家的监管就越松,所以距离首都越远,客运公司的Pi(单位距离价格)越大,形式化的说,如果把高速路网看成一棵以首都为根的有根树,i号城市是j号城市的某个祖先,那么一定存在Pi<=Pj。

题目描述

大宁成为了国家统计局的调查人员,他需要对现在的高速路网进行一次调查,了解从其他每一个城市到达首都1号城市所花费的金钱。

因为有非常多转车(或不转车)的抵达首都的方法,所以人工计算这个结果是十分复杂的。大宁非常的懒,所以请你编写一个程序解决它。

输入输出格式

输入格式:

第 1 行包含1个非负整数 n,表示城市的个数。

第 2 到 n 行,每行描述一个除首都之外的城市。其中第 i 行包含4 个非负整数 Fi,Si,Pi,Qi,分别表示 i号城市的父亲城市,它到父亲城市高速公路的长度,以及乘车价格的两个参数。

输出格式:

输出包含 n-1 行,每行包含一个整数。

其中第 i 行表示从 i+1号城市 出发,到达首都最少的乘车费用。

输入输出样例

输入样例#1:  复制
6
1 9 3 0
1 17 1 9
1 1 1 6
4 13 2 15
4 9 2 4
输出样例#1:  复制
27
26
7
43
24

说明

对于前40%的数据1<=n<=1000。

对于另外20%的数据 满足从第i(i≠1)个城市出发的高速公路连向第i-1个城市。

对于所有的数据1<=n<=1000000,0<=Pi,Qi<=2^31-1,保证结果不会大于2^63-1。

大样例下载:https://pan.baidu.com/s/1jIpbrhG





题解

我们把一个点到根的路径上的点逐个标号,那么就有dp方程f[i] = min{f[j] + Q[i] * (sum[i] - sum[j]) + P[i]};
这显然是1D1D方程,我们把只与i有关的常数项去掉化为: f[j] = Q[i] * sum[j] + f[i]
就可以斜率优化了。
点为(sum[j],f[j])单调递增,我们要截距f[i]最小维护下凸包
————————————————————————————
这是对于一条到根路径上的情况,但题目求的是一棵树
我们可以dfs,由dfs的性质,到达该点时前几层dfs一定是该点到根的路径,我们只要维护单调队列保证从父亲下来时单调队列不受其他儿子影响【也就是从根一路下来维护出的单调队列,而没有经过其他儿子的出队操作】
————————————————————————————
这怎么维护?听说原题是主席树维护可持续化数组【蒟蒻不会O(nlogn)QAQ,只好用O(n)。。】
可以这样想,我们假设每次向儿子dfs完后,儿子都很负责任地将单调队列还原成dfs前的模样
那么我们每次只需保证一次dfs结束时单调队列和dfs近来时是一样的
我们看看我们一次dfs对单调队列做了什么修改:
①若干队首出队【只是头指针移动,元素并未改变】
②若干队尾出队【只是尾指针移动,元素并未改变】
③一个元素进队,替换掉原数组中的一个位置上的元素
那么我们还原就很简单了,每次进来时记录单调队列的首尾和之后被替换掉的位置上的数,在dfs结束时还原回来就OK啦

期望复杂度O(n)【其实最坏也是可以O(nlogn)的,但几乎不可能】
#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
#include<algorithm>
#define LL long long int
#define REP(i,n) for (int i = 1; i <= (n); i++)
#define Redge(u) for (int k = head[u]; k != -1; k = edge[k].next)
using namespace std;
const int maxn = 1000005,maxm = 100005,INF = 1000000000;
inline int RD(){
	int out = 0,flag = 1; char c = getchar();
	while (c < 48 || c > 57) {if (c == '-') flag = -1; c = getchar();}
	while (c >= 48 && c <= 57) {out = (out << 1) + (out << 3) + c - '0'; c = getchar();}
	return out * flag;
}
int n,ls[maxn],rb[maxn],Q[maxn],P[maxn];
LL f[maxn],sum[maxn];
int q[maxn],h,t;
double slope(int u,int v){
	if (sum[u] == sum[v]) return INF;
	return (double)(f[u] - f[v]) / (sum[u] - sum[v]);
}
void dfs(int u){
	while (h < t && slope(q[h + 1],q[h]) < Q[u]) h++;
	int p = q[h];
	f[u] = f[p] + Q[u] * (sum[u] - sum[p]) + P[u];
	int temp = h,tem = t,te;
	while (h < t && slope(q[t],q[t - 1]) > slope(u,q[t])) t--;
	te = q[++t]; q[t] = u;
	for (int k = ls[u]; k; k = rb[k]){
		h = temp;
		dfs(k);
	}
	q[t] = te; t = tem;
}
int main(){
	n = RD(); int fa;
	for (int i = 2; i <= n; i++){
		fa = RD(); sum[i] = sum[fa] + RD();
		Q[i] = RD();P[i] = RD();
		rb[i] = ls[fa]; ls[fa] = i;
	}
	for (int k = ls[1]; k; k = rb[k]) q[h = t = 0] = 1,dfs(k);
	for (int i = 2; i <= n; i++) printf("%lld\n",f[i]);
	return 0;
}




评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值