题目背景
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<=231-1,保证结果不会大于263-1。
思路:
这道题我们可以设S[i]
代表首都到i城市的距离, dp[i]
代表第i个城市到首都最少乘车费用,那么我们可以得到递推式:
dp[i] = min(dp[j] + P[i] * (S[i] - S[j]) + Q[i]) (0 <= j < i)
上面这个式子的时间复杂度为O(n ^ 2),由于n的范围为1e6,因此我们必须优化为O(n)的做法,我们发现,该式符合斜率优化的特征:
dp[j]为变量,P[i] * S[i]和Q[i]为不变量,而P[i]*S[j]为混合量
我们开始将混合量去掉:
第一步:由于该题所有城市组成一棵树,1号首都为根结点。我们设j 为 k的子节点(不一定是直接子节点)且对于求dp[i]
,j比k优,那么:
dp[j] + P[i] * (S[i] - S[j]) + Q[i] < dp[k] + P[i] * (S[i] - S[k]) + Q[i]
dp[j] - P[i] * S[j] < dp[k] - P[i] * S[k]
(dp[j] - dp[k]) / (S[j] - S[k]) < P[i]
其中(dp[j] - dp[k]) / (S[j] - S[k])
就为求解的斜率
第二步
设P(p, q)为点p和点q之间的斜率,那么假设j, p, q都是i的祖宗节点且q是j的祖宗节点,p是q的祖宗节点且P(p, q) > P(q, j)。我们讨论所有的情况
(1) P[i] < P(q, j) < P(p, q)
此时,q优于j,p优于q,因此p是最优解
(2) P(q, j) < P[i] < P(p, q)
此时,j优于q,p优于q,因此j,p中的一个是最优解
(3) P(q, j) < P(p, q) < P[i]
此时,j优于q,q优于p,因此j是最优解
我们会发现,在满足该种的情况下,无论如何,q都不是最优解,也就是中间的那个永远不是最优,因此,我们可以用单调队列来维护斜率上升的队列来O(1)的求解dp[i]
第三步:
因为这是一个树,在遍历树的时候,遍历完子节点回溯到父节点的时候需要对队列进行还原,我们发现手动模拟的队列数组q虽然tail,head改变,但是存储的内容只有一个位置发生改变,其余改变的只有head和tail,数组中的东西依旧保存。因此我们只需要维护每次的head,tail和改变的那个值即可,详细请看代码:
/*************************************************************************
> File Name: p.cpp
> Author: Zcy
> Mail: 296763002@qq.com
> Created Time: 三 1/23 18:16:17 2019
************************************************************************/
#include <stdio.h>
#include <vector>
#include <ctype.h>
#define ll long long
using namespace std;
//快速读
inline int read() {
int num=0;
char ch=0;
while (!isdigit(ch)) {
ch = getchar();
}
while (isdigit(ch)) {
num = (num<<3) + (num<<1) + (ch^48);
ch = getchar();
}
return num;
}
//快速读
inline ll readl() {
ll num=0;
char ch=0;
while (!isdigit(ch)) {
ch = getchar();
}
while (isdigit(ch)) {
num = (num<<3) + (num<<1) + (ch^48);
ch = getchar();
}
return num;
}
struct node
{
int inx;
ll val;
void set(int ninx, ll nval) {
inx = ninx;
val = nval;
}
};
ll P[1000005] = {0}, Q[1000005] = {0}, dp[1000005] = {0}, s[1000005] = {0};
vector<node> v[1000005];
int q[1000005];
void set(int j, int i) {
dp[i] = dp[j] + P[i] * (s[i] - s[j]) + Q[i];
}
double getp(int j, int i) {
return 1.0 * (dp[i] - dp[j]) / (s[i] - s[j]);
}
void dfs(int inx, int tail, int head) {
while (head + 2 <= tail && getp(q[head], q[head + 1]) <= P[inx]) head++;
set(q[head], inx);
while (head + 2 <= tail && getp(q[tail - 1], inx) <= getp(q[tail - 2], q[tail - 1])) tail--;
int flag = q[tail];
q[tail++] = inx;
for (int i = 0; i < v[inx].size(); i++) {
node t = v[inx][i];
s[t.inx] = s[inx] + t.val;
dfs(t.inx, tail, head);
}
q[tail - 1] = flag;
return;
}
int main () {
int n, father;
ll x;
node t;
n = read();
for (int i = 2; i <= n; i++) {
father = read();
x = readl();
P[i] = readl();
Q[i] = readl();
t.set(i, x);
v[father].push_back(t);
}
q[1] = 1;
dfs(1, 1, 1);
for (int i = 2; i <= n; i++) {
printf("%lld\n", dp[i]);
}
return 0;
}
如果有写的不对或者不全面的地方 可通过主页的联系方式进行指正,谢谢