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. nyoj 737 石子合并(区间DP)

    737-石子合并(一) 内存限制:64MB 时间限制:1000ms 特判: No通过数:28 提交数:35 难度:3 题目描述:     有N堆石子排成一排,每堆石子有一定的数量.现要将N堆石子并成为 ...

  2. UWP GraphQL数据查询的实现

    1. 缘起 Facebook 的移动应用从 2012 年就开始使用 GraphQL.GraphQL 规范于 2015 年开源,现已经在多种环境下可用,并被各种体量的团队所使用. 在这个链接可以看到更多 ...

  3. let和const总结(ES6)

    文章目录 let const 1. let要好好用 1. 基本用法 2. let声明的变量不存在变量提升 3. TDZ(temporal dead zone)暂时性死区 4. 不允许重复声明 2. 块 ...

  4. WebGPU学习系列目录

    介绍 大家好,本系列从0开始学习WebGPU API,并给出相关的demo. WebGPU介绍 WebGPU相当于DX12/Vulkan,能让程序员更灵活地操作GPU,从而大幅提升性能. 为什么要学习 ...

  5. SSM(Spring+SpringMVC+Mybatis)框架整合

    1.数据准备 SET FOREIGN_KEY_CHECKS=0; -- ---------------------------- -- Table structure for `admin` -- - ...

  6. Android Studio 安装教程

    前言 前段时间周围有很多认识的人学习Android,看蛮多人在装Android Studio,然而看他们装的过程不是那么顺利?然后也有高中同学来问我,于是乎就自己也试着去装了下,也方便日后学习Andr ...

  7. linux 删除.svn文件

    linux删除当前目录及其子目录下的.svn文件,linux下删除全部的.svn文件 find . -name "*.svn" -type d -print -exec rm -r ...

  8. 腾讯云推出一站式 DevOps 解决方案 —— CODING DevOps

    在产业互联网的大背景下,如何将人工智能.大数据等前沿技术与实体产业相结合,推动传统企业转型升级,已经成为每一个企业不得不思考的问题.落后的软件研发能力已经拖慢了中国大量企业的数字化转型进程. 为了满足 ...

  9. SQL SERVER查询数据库所有表的大小,按照记录数降序排列

    SELECT B.NAME,A.ROW_COUNT FROM SYS.DM_DB_PARTITION_STATS A,SYS.OBJECTS BWHERE A.OBJECT_ID=B.OBJECT_I ...

  10. 松软科技web教程:JavaScript Switch 语句

    switch 语句用于基于不同条件执行不同动作. JavaScript Switch 语句 请使用 switch 语句来选择多个需被执行的代码块之一. 语法 switch(表达式) { case n: ...