Problem

Background

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

Description

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

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

Input Format

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

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

Output Format

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

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

Sample

Input 1

6
1 9 3 0
1 17 1 9
1 1 1 6
4 13 2 15
4 9 2 4

Output 1

27
26
7
43
24

Input 2 and Output 2

百度网盘

Range

对于前 \(40\%\) 的数据 \(1\le n\le1000\)。

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

对于所有的数据 \(1\le n\le1000000,0\le P_i,Q_i\le2^{31}–1\),保证结果不会大于 \(2^{63}–1\)。

Algorithm

\(DP\),斜率优化

Mentality

看看这个题目,其实一下子就能想到我们可以在每一条从根节点到叶子节点的路径上线性 \(DP\) 。

那么方程也异常简单:设 \(f[i]\) 为从 \(i\) 点到达根节点所需最小费用,\(pre_i\) 为 \(i\) 到根节点路径上所有点的集合,\(lj[i]\) 为 \(i\) 到根节点的距离,那么自然有:

\[f[i]=Min_{j\in pre_i}(f[j]+(lj[i]-lj[j])*P[i]+Q[i])
\]

当然,这个 \(DP\) 方程的复杂度是不过关的。按照一贯的套路,这种很容易想式子的 \(DP\) 题总满足决策单调性。所以我们尝试一下列出它的决策之间有什么关系。

设 \(i\) 的两个决策点为 \(j,k\) ,其中 \(j\in pre_k\) ,\(j,k\in pre_i\) 。

如果从点 \(k\) 转移比从点 \(j\) 转移更优,那么自然有:

\[f[k]+(lj[i]-lj[k])*P[i]+Q[i]<f[j]+(lj[i]-lj[j])*P[i]+Q[i]\\
f[k]-lj[k]*P[i]<f[j]-lj[j]*P[i]\\
f[k]-f[j]<(lj[k]-lj[j])*P[i]\\
\frac{f[k]-f[j]}{lj[k]-lj[j]}<P[i]
\]

我们发现,由于题目有 \(P_i>P_{pre_i}\) 的条件,那么当我们在一条链上 \(DP\) 时,一旦从某一点开始满足上式,则这个点的子树内所有点一直都会满足上式。换句话说,对于这个点的子树内所有点而言,从 \(k\) 转移总会比从 \(j\) 转移更优

换而言之,此题满足决策单调性中的斜率优化的条件。

那么由于 \(P[i]\) 递增,我们只需要维护一个斜率单调递增的决策点队列即可。

但是这是一棵树,怎么办?

很简单,每个点在更改决策点的单调队列时只有三个操作:

  • 不断 \(++head\) ,直到队首斜率不满足上式
  • 不断 \(--tail\) ,直到对尾斜率符合单调性
  • \(++tail\) ,在队列尾部插入当前决策点

那么我们发现,每到一个新的点,其实我们只是修改了一下 \(head\) 和 \(tail\) 的值,并修改了队列中的一个元素。则我们只需要记住修改之前的那个元素的值,修改之前的队首对尾的位置即可,当我们退出当前点的时候,只需要照着之前记录的值改回去就好了!

但是对于这题还不够。

一般而言,我们的斜率优化总会是 \(O(n)\) 的,因为每个点最多只会被入队出队一次,所以总体复杂度只会取决于决策点个数而不是枚举决策点。

但是在这题里,一个决策点可能在这个点里入队,在下一个点里出队,而回溯回来的时候又再次入队,因而不符合每个点最多只会入队出队一次的规则!,换句话说,这样做的话,复杂度是错误的。

那接下来又怎么做呢?

由于我们的决策点队列里,相邻两点之间的斜率是单调的,所以我们完全可以通过二分来找到应该 \(++head,--tail\) 所达到的位置!

只需要找到最左边的,不满足斜率不等式的位置,以及最右边的,不满足斜率单调递增的位置即可。

当然,二分可能有点小细节,可以看看我的代码。(良心注释)

Code

#include <cstdio>
#include <iostream>
using namespace std;
int n;
int cntr, head[1000001], to[1000001], nx[1000001], w[1000001];
int Q[1000001];
int que[1000001], h, t;
long long P[1000001], lj[1000001], f[1000001];
void addr(int u, int v, int W) {
cntr++;
to[cntr] = v, nx[cntr] = head[u], w[cntr] = W;
head[u] = cntr;
}
double slope(int a, int b) {
return (double)(f[b] - f[a]) / (lj[b] - lj[a]);
} //斜率不等式
void DP(int x) {
int p, tph = h, tpt = t, tpw, l = h + 1, r = t,
ans = h; // l 的初值为 h+1,因为我们用来比较的是 mid-1 与 mid
// 两个位置的斜率。之所以不用 l=h,r=t-1 作为初值并比较 mid 与
// mid+1 ,是因为根据我们做斜率题的时候 h
// 总会在最右边的符合不等式的位置的右一个。这样二分出來的位置刚刚好;ans
// 的初值为 h,因为如果找不到相应的位置,那就不需要修改 h。
while (l <= r) {
int mid = (l + r) >> 1;
if (slope(que[mid - 1], que[mid]) <= P[x])
l = mid + 1, ans = mid;
else
r = mid - 1;
}
h = ans, p = que[h];
f[x] = f[p] + (lj[x] - lj[p]) * P[x] + Q[x];
l = h, r = t - 1, ans = t; //这里的理由与队首差不多,都是为了结果正确性
while (l <= r) {
int mid = (l + r) >> 1;
if (slope(que[mid], que[mid + 1]) >= slope(que[mid + 1], x))
r = mid - 1, ans = mid;
else
l = mid + 1;
}
t = ans;
tpw = que[++t], que[t] = x; //记录即将被更改的位置的元素并更新
for (int i = head[x]; i; i = nx[i]) {
lj[to[i]] = lj[x] + w[i]; //更新路径长度
DP(to[i]);
}
que[t] = tpw;
h = tph, t = tpt; //修改回进入这个点之前的状态
}
int main() {
freopen("3994.in", "r", stdin);
freopen("3994.out", "w", stdout);
cin >> n;
int F, W;
for (int i = 2; i <= n; i++) {
scanf("%d%d%lld%d", &F, &W, &P[i], &Q[i]);
addr(F, i, W);
}
t = -1; //最开始队列是空的,所以 tail=head-1
DP(1);
for (int i = 2; i <= n; i++) printf("%lld\n", f[i]);
}

【Luogu P3994】高速公路的更多相关文章

  1. 洛谷 P3994 高速公路

    https://www.luogu.org/problemnew/show/P3994 设dp[i] 表示第i个城市到根节点的最小花费 dp[i]=min{ (dis[i]-dis[j])*P[i]+ ...

  2. P3994 高速公路 树形DP+斜率优化+二分

    $ \color{#0066ff}{ 题目描述 }$ C国拥有一张四通八达的高速公路网树,其中有n个城市,城市之间由一共n-1条高速公路连接.除了首都1号城市,每个城市都有一家本地的客运公司,可以发车 ...

  3. 洛谷 P3994 高速公路(斜率优化)

    题目链接 题意:给出一棵树,\(1\) 号点为根,边上有边权. 每个点有两个参数 \(p_i,q_i\) 如果你想从 \(i\) 号点到与其距离为 \(d\) 的 \(j\) 号点,那么你需花费 \( ...

  4. P3994 高速公路

    题目链接 题意分析 这是一道树上斜率优化题 首先 \[dp[i]=min\{dp[j]+(dis[i]-dis[j])* p[i]+q[i]\}(j∈Pre_i)\] 那么就是 \[p[i]=\fra ...

  5. [Luogu 2221] HAOI2012 高速公路

    [Luogu 2221] HAOI2012 高速公路 比较容易看出的线段树题目. 由于等概率,期望便转化为 子集元素和/子集个数. 每一段l..r中,子集元素和为: \(\sum w_{i}(i-l+ ...

  6. 【题解】Luogu P2221 [HAOI2012]高速公路

    原题传送门 这道题还算简单 我们要求的期望值: \[\frac{\sum_{i=l}^r\sum_{j=l}^rdis[i][j]}{C_{r-l+1}^{2}}\] 当然是上下两部分分别求,下面肥肠 ...

  7. 【Luogu】P2221高速公路(线段树乱搞)

    题目链接 这题……我从一开始就想歪了qwq. 为了缅怀逝去的一小时我就把我的30分暴力思路放上来. 首先我们观察枚举的区间.假设我们要枚举的范围是1~5之间的四条路,为了方便我们把它们叫做abcd. ...

  8. luogu P2221 [HAOI2012]高速公路题解

    题面 很套路的拆式子然后线段树上维护区间和的题.一般都是把式子拆成区间内几个形如\(\sum i*a_i, \sum i^2 * a_i\)的式子相加减的形式. 考虑一次询问[l,r]的答案怎么算: ...

  9. 【BZOJ2752】【Luogu P2221】 [HAOI2012]高速公路

    不是很难的一个题目.正确思路是统计每一条边被经过的次数,但我最初由于习惯直接先上了一个前缀和再推的式子,导致极其麻烦难以写对而且会爆\(longlong\). 推导过程请看这里. #include & ...

随机推荐

  1. 如何使用Jmeter进行压力测试

    Jmeter做压力测试的操作:Jmeter不仅可以做接口测试,还可以做压力测试,下面介绍介绍如何jmeter进行简单地压力测试.具体步骤如下: 第一步:添加请求,这里不介绍具体步骤,详见(https: ...

  2. CCF-CSP题解 201812-3 CIDR合并

    题目想求与给定前缀列表等价的包含IP前缀数目最少的前缀列表. 首先是怎么存储前缀列表.用一个long long存储IP地址,再存一个前缀长度,封装在一个结构体里\(<ipNum, len> ...

  3. 2016/11/10 kettle概述

    ETL(Extract-Transform-Load,即抽取,转换,加载),数据仓库技术,是用来处理将数据从来源(以前做的项目)经过抽取,转换,加载到达目的端(正在做的项目)的过程.也就是新的项目需要 ...

  4. Gradle-任务

    任务结果标签 当 Gradle 执行一个任务时,它会在控制台和 Tooling API 根据任务结果给任务打标签. 这些标签是根据任务是否有操作,是否应该执行操作,是否执行了操作以及这些操作做了哪些改 ...

  5. Android设置EditText不可编辑

    版权声明:本文为xing_star原创文章,转载请注明出处! 本文同步自http://javaexception.com/archives/224 禁用EditText 这个其实很简单,最简单的一种方 ...

  6. Redis—数据备份与恢复

    https://www.cnblogs.com/shizhengwen/p/9283973.html https://blog.csdn.net/w2393040183/article/details ...

  7. Consul初探-服务注册和发现

    前言 经过上一篇的学习,现在已经来到了服务注册发现环节:Consul 的核心功能就是服务注册和发现,Consul 客户端通过将自己注册到 Consul 服务器集群,然后等待调用方去发现服务,实现代理转 ...

  8. RabbitMQ 在.Net 中的使用

    RabbitMQHelper public static class RabbitMQHelper { // 定义 RabbitMQ 基本参数 private static string HostNa ...

  9. vue render函数解析

    一.render 函数的作用: 写一些vue.js的template太繁琐,利用render,可以使用js来生成模板,更加灵活和简便. 二.使用render前提: 官网也说了.在深入渲染函数之前推荐阅 ...

  10. QGIS练手 - 标注

    又熬夜了... QGIS的标注就是标签,在QGIS3.x中有了改进. 不得不说,就光速度这一项,就能把ArcMap按在地上摩擦,更别说各种高级的标注样式了——除了标注功能面板UI有点“缺审美化”就是了 ...