L2759 蜜袋鼯(フクロモモンガ) 题解
题面
{% note 题面原文 %}
フクロモモンガの \(\rm{JOI}\) 君が住んでいる森にはユーカリの木が \(N\) 本生えており,それらの木には \(1\) から \(N\) の番号がついている.木 \(i\) の高さは \(H_i\) メートルである.
\(\rm{JOI}\) 君が相互に直接飛び移ることのできる木の組が \(M\) 組あり,各組の木の間を飛び移るためにかかる時間が定まっている. \(\rm{JOI}\) 君が木の間を飛び移っている間は,地面からの高さが \(1\) 秒あたり \(1\) メートル下がる.すなわち,\(\rm{JOI}\) 君の現在の地面からの高さが \(h\) メートル,木の間を飛び移るためにかかる時間が \(t\) 秒であるとき,飛び移った後の地面からの高さは \(h − t\) メートルとなる.ただし,\(h − t\) が \(0\) よりも小さくなる場合や行き先の木の高さよりも大きくなる場合は飛び移ることができない.
さらに,\(\rm{JOI}\) 君は木の側面を上下に移動することによって,地面からの高さを \(0\) メートルから今いる木の高さの範囲で増減させることができる.JOI 君が地面からの高さを 1 メートル増加または減少させるためには \(1\) 秒の時間がかかる.
\(\rm{JOI}\) 君は,木 \(1\) の高さ \(X\) メートルの位置から木 \(N\) の頂上 (高さ \(H_N\) メートルの位置) に行こうとしており,そのためにかかる時間の最小値を知りたい.
課題
各木の高さと,\(\rm{JOI}\) 君が直接飛び移ることができる木の組の情報と,最初 \(\rm{JOI}\) 君がいる場所の高さが与
えられる.木 \(N\) の頂上に行くためにかかる時間の最小値を求めるプログラムを作成せよ.
入力
標準入力から以下のデータを読み込め.
• \(1\) 行目には,整数 \(N, M, X\) が空白を区切りとして書かれている.これは,木の本数が \(N\) 本,移動できる木の組が \(M\) 組あり,最初 \(\rm{JOI}\) 君が木 \(1\) の高さ \(X\) メートルの位置にいることを表す.
• 続く \(N\) 行のうちの \(i\) 行目 \((1 \leq i \leq N)\) には,整数 \(H_i\) が書かれている.これは,木 \(i\) の高さが \(H_i\) メートルであることを表す.
• 続く \(M\) 行のうちの \(j\) 行目 \((1 \leq j \leq M)\) には,整数 \(A_j\), \(B_j\), \(T_j\) \((1 \leq A_j \leq N, 1 \leq B_j \leq N, A_j \neq B_j)\) が空白を区切りとして書かれている.これは,木 \(A_j\) と木 \(B_j\) の間を相互に \(T_j\) 秒で飛び移ることができることを表している.また,\(1 \leq j < k \leq M\) ならば,\((A_j, B_j) \neq (A_k, B_k)\) および \((A_j, B_j) \neq (B_k, A_k)\) を満たす.
出力
標準出力に,木 \(1\) の高さ \(X\) メートルの位置から木 \(N\) の頂上に行くためにかかる時間の最小値を秒単位で表す整数を \(1\) 行で出力せよ.ただし,そのような方法がない場合は代わりに \(−1\) を出力せよ.
制限
すべての入力データは以下の条件を満たす.
• \(2 \leq N \leq 100 000\).
• \(1 \leq M \leq 300 000\).
• \(1 \leq Hi \leq 1 000 000 000 (1 \leq i \leq N)\).
• \(1 \leq T j \leq 1 000 000 000 (1 \leq j \leq M)\).
• \(0 \leq X \leq H1\).
小課題
小課題 1 [25 点]
以下の条件を満たす.
• \(N \leq 1 000\).
• \(M \leq 3 000\).
• \(Hi \leq 100 (1 \leq i \leq N)\).
• \(T j \leq 100 (1 \leq j \leq M)\).
小課題 2 [25 点]
以下の条件を満たす.
• \(X = 0\).
小課題 3 [50 点]
追加の制限はない.
{% note 题面翻译 %}
蜜袋鼯 \(\rm{JOI}\) 君住着的森林里长着编号为 \(1\) 到 \(N\) 的 \(N\) 棵桉树。第 \(i\) 棵树的高度是 \(H_i\) 米。
\(\rm{JOI}\) 君能在其中的 \(M\) 对桉树之间直接飞行,在各对树木之间飞行所需的时间是固定的。当 \(\rm{JOI}\) 君在树木之间飞行的时候,他离地面的高度会每秒下降 \(1\) 米。也就是说,如果 \(\rm{JOI}\) 君现在离地高度是 \(h\) 米,在树木之间飞行需要 \(t\) 秒,那么飞行之后的离地高度就会变成 \(h-t\) 米。当 小于 \(0\) 或大于目标树木的高度时则不能飞行。
\(\rm{JOI}\) 君还能沿着树的侧面上下移动,使得他的离地高度在 \(0\) 到当前所在树木高度的范围内变化。\(\rm{JOI}\) 君每使自己的离地高度增加或减少 \(1\) 米都需要 \(1\) 秒的时间。
\(\rm{JOI}\) 君要从 \(1\) 号树木上高度为 \(X\) 米的位置出发,到树木 \(N\) 的顶端(高度为 \(H_N\) 米的位置)去。他想知道为了达成这个目标所需时间的最小值。
给出各棵树木的高度、\(\rm{JOI}\) 君能直接飞行的树木对和 \(\rm{JOI}\) 君最初所在位置的高度,请求出到达树木 \(N\) 顶端所需时间的最小值。
输入格式
第一行包含三个以空格分开的整数 \(N,M\) 和 \(X\) ,意义分别与题目描述中的 \(N,M\) 和 \(X\) 相同。
接下来 \(N\) 行中,第 \(i\) 行 \((1 \leq i \leq N)\) 有一个整数 \(H_i\) ,表示树木 \(i\) 的高度是 \(H_i\) 米。
接下来 \(M\) 行中,第 \(j\) 行 \((1 \leq j \leq M)\) 有三个以空格分开的整数 \(A_j\), \(B_j\), \(T_j\) \((1 \leq A_j \leq N, 1 \leq B_j \leq N, A_j \neq B_j)\) ,表示 IOI 君能花 \(T_j\) 秒的时间从 \(A_j\) 飞到 \(B_j\) 或从 \(B_j\) 飞到 \(A_j\) 。
对于任意 \(1 \leq j < k \leq M\) ,满足 \((A_j, B_j) \neq (A_k, B_k)\) 且 \((A_j, B_j) \neq (B_k, A_k)\) 。
输出格式
输出到标准输出,仅一行一个整数,表示从树木 \(1\) 上高度为 \(X\) 米处移动到树木 \(N\) 顶端所需时间的最小值(单位:秒)。如果不能到达目的地则输出 \(-1\) 。
(子任务数据看原文)
解析
首先我们考虑一下我们的策略。
无论给出何种方案,我们都需要遵守一个原则:非必要不爬升,且爬升时只爬升至足够飞过去的高度即可。
{% note 简单说明 %}
对于前半句,我们这样分析:
我们假设可以突破高度只能大于等于0的限制。
那我们就可以持续进行飞行操作,知道到达终点再向上爬到树顶。
也就是说,我们将我们的操作从一系列的“飞跃-爬升”操作变成了一串连续的飞跃操作和一个爬升操作。
显然,只要我们选择的路径一定,我们最后爬升的路程一定是一定的。
我们不妨设其为 \(h\) 。
如果我们在这种情况下,选择在中间进行一次爬升操作,假设我们爬升的高度为 \(h'\) 。
那么,我们到达最后的高度就是\(H_N - h + h'\)。
然而,我们总共还是爬升了 \(h-h'+h'=h\) 的高度,总共爬升操作所用的时间也是不变的。
更何况我们还会遇到到一棵树正上方时高度大于树高的时候,这时候我们就只能降低高度,而在最后多的时间来爬升刚才降低的那一段距离。
这会导致如果我们瞎爬升的话,结果会劣于非必要不爬升的结果。
那么对于后半句,我们这样分析:
假设一种极限情况,一路上的树一棵比一棵矮,且每一次从树顶开始飞行都会飞跃目标树。
那么如果我们选择爬升的高度太多,就会导致爬升高度的浪费。
我们完全可以选择一种极端的情况,那么就是让我们的高度保持为0即可。
我们在每次飞跃一条边(假设其为 \((u,v)\) )时,我们如果之前已经保持了高度为0的话,我们就只需爬升 \(w_{(u,v)}\) 的高度即可,即到达 \(v\) 点时高度仍然为零。而如果我们仍有高度\(x\)的话,我们就爬升 (x-w[(u,v)]>=0)?0:(w[(u,v)]-x) 即可。
如果按照这样操作的话,我们只会在达到高度为0之前的时候才可能下降高度以适配较低的树高,最终得到的就是最优的结果。
Q.E.D.
之后对每一条边进行分析。
对于每一条边 \((u,v)\) ,会有以下四种情况:
- 到达点正上方时的高度大于树高。
需要先向下爬到高度为 \(h_v + w_{(u,v)}\) 的点才能从该边飞过,可以证明这是最优选择。 - 在飞行途中不得不落地。
我们无论如何都无法通过这一条边,只能在向有向图中加边时忽略这一条边。 - 不向上爬无法正常飞到下一个点。
根据上面的证明,我们只需向上爬升至高度 \(w_{(u,v)}\) ,保证尽量靠近地面。 - 可以正常飞到下一个点。
直接飞跃即可。
这样这个问题就变成了一个最短路问题。
而对于最短路的寻找,我们采用dijkstra算法。
代码如下:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 100005, M = 300005;
int h[N], ne[M << 1], e[M << 1], w[M << 1], idx;
void add(int a, int b, int c)
{
e[++idx] = b, ne[idx] = h[a], h[a] = idx, w[idx] = c;
return;
}
#define bianli(x) for(int i=h[x];i;i=ne[i])
int height[N];
int n, m;
ll sum[N];
int H[N];
bool vis[N];
struct node
{
ll v;
int id;
bool operator <(const node &A)const
{
return v > A.v;
}
};
priority_queue<node> q;
void dijkstra(int at)
{
memset(sum, 0x7f, sizeof(sum));
q.push({ 0,1 });
H[1] = at;
sum[1] = 0;
ll v;
while(!q.empty())
{
node now = q.top();
q.pop();
if(vis[now.id])continue;
vis[now.id] = 1;
int h = H[now.id];
bianli(now.id)
{
if(h - w[i] > height[e[i]])//飞越了树顶
{
v = sum[now.id] + h - w[i] - height[e[i]] + w[i];
if(sum[e[i]] > v)
{
sum[e[i]] = v;
H[e[i]] = height[e[i]];
q.push({ sum[e[i]],e[i] });
}
}
else if(h - w[i] < 0)//触及了地面
{
v = sum[now.id] + w[i] - h + w[i];
if(sum[e[i]] > v)
{
sum[e[i]] = v;
H[e[i]] = 0;
q.push({ sum[e[i]],e[i] });
}
}
else//正常走
{
if(sum[e[i]] > sum[now.id] + w[i])
{
sum[e[i]] = sum[now.id] + w[i];
H[e[i]] = h - w[i];
q.push({ sum[e[i]],e[i] });
}
}
}
}
}
int main()
{
int at;
scanf("%d%d%d", &n, &m, &at);
for(int i = 1; i <= n; i++)scanf("%d", &height[i]);
int a, b, c;
while(m--)
{
scanf("%d%d%d", &a, &b, &c);
if(height[a] >= c)add(a, b, c);
if(height[b] >= c)add(b, a, c);
}
dijkstra(at);
if(sum[n] == 1e18)puts("-1");
else printf("%lld\n", sum[n] + height[n] - H[n]);
return 0;
}

浙公网安备 33010602011771号