T1 ###寻找有向图中的最小疲惫路径###
题目描述
有一张 n
个点 m
条边的有向图,每条边上有一个正整数边权,你要顺着图上的有向边从 1
号点走到 n
号点。
假设你经过的边边权依次为 (w_1, w_2, \dots, w_t),则你的疲惫程度为
f
(
w
)
=
max
i
=
1
t
w
i
×
i
.
\ f(w) =\max_{i=1}^{t} w_i \times i\,.
f(w)=i=1maxtwi×i.
你需要找到最小疲惫程度的路径。
输入格式
第一行两个空格分隔的正整数 n, m
,表示有向图的点数和边数。有向图的点用 1
到 n
编号。
接下来 m
行每行描述一条有向图的边,一行三个用空格分隔的正整数 a, b, c
,表示一条从编号为 a
的点出发,到达编号为 b
的点,边权为 c
的有向边。
可能有重边和/或自环。
输出格式
输出一个正整数,表示路径可能疲惫程度的最小值。
样例输入1
3 3
1 2 5
2 3 4
1 3 6
样例输出1
6
样例解释1
路径 1→2→3
的疲惫程度为 8
,路径 1→3
的疲惫程度为 6
。可能疲惫程度最小值为 6
。
样例 2 3 4 5
见下发文件。
数据范围
对于所有数据,(2
≤
n
,
m
≤
\leq n, m \leq
≤n,m≤ 3
×
\times
× 105),(1
≤
\leq
≤ w_i
≤
\leq
≤ 109),至少有一条从 1
号点到 n
号点的路径。
- Subtask 1(20pts):(n, m ≤ \leq ≤ 20) , ( w i ≤ w_i \leq wi≤ 104)。
- Subtask 2(20pts):(n, m ≤ \leq ≤ 100) , ( w i ≤ w_i \leq wi≤ 104)。
- Subtask 3(20pts):(n, m ≤ \leq ≤ 2000)。
- Subtask 4(20pts):(n, m ≤ 5 × \leq 5 \times ≤5× 104)。
- Subtask 5(20pts):(n, m ≤ 3 × \leq 3 \times ≤3× 105)。
题解
思路
题目要求找到最小疲惫程度的路径,我们可以使用二分查找的方法来寻找这个最小值。具体地,我们可以设定一个疲惫程度的上限,然后检查是否存在一条路径满足这个上限。如果存在,我们就尝试降低这个上限;如果不存在,我们就提高这个上限。
代码
#include<bits/stdc++.h>
#define ll long long // 使用"ll"作为长整型数据的别名
using namespace std;
const int N=3e5+5; // 节点的最大数量
int n, m, x, y, z, vis[N], cur=1, pre; // 声明全局变量
vector<pair<int,int>> v[N]; // 邻接表:节点 -> {邻居,权重}
// 此函数检查是否可以用给定的"mid"作为最大疲劳值到达目的地
bool ok(long long mid){
vector<int> id[2]; // 使用2个向量在当前节点集和前一个节点集之间交替
fill(vis+1, vis+1+n, 0); // 将"vis"数组初始化为0
vis[1] = 1; // 标记起始节点已访问
id[cur].push_back(1); // 将起始节点添加到当前节点集
for(int i=1; i<n; i++){ // 循环n-1次
swap(cur, pre); // 在当前和前一个节点集之间交替
id[cur].clear(); // 清除当前节点集
for(int x : id[pre]) // 对于前一个节点集中的每个节点
for(auto p : v[x]){ // 对于该节点的每个邻居
int y = p.first; // 邻居
// 如果邻居没有被访问,且没有超过疲劳限制
if(!vis[y] && 1ll * i * p.second <= mid) {
vis[y] = 1; // 标记邻居已访问
id[cur].push_back(y); // 将邻居添加到当前节点集
}
}
}
return vis[n]; // 如果目的地被访问,则返回true,否则返回false
}
int main(){
scanf("%d%d", &n, &m); // 读取节点和边的数量
for(int i=1; i<=m; i++) // 读取边
scanf("%d%d%d", &x, &y, &z), // 读取起始节点、结束节点和权重
v[x].push_back({y, z}); // 将边添加到邻接表
ll l=1, r=3e14, ans=0; // 二分查找的界限和答案
// 二分查找最小疲劳值
while(l <= r){
ll mid = (l + r) / 2; // 计算中点
if(ok(mid)) { // 如果可以用"mid"作为最大疲劳值到达目的地
ans = mid; // 更新答案
r = mid - 1; // 将右界移向左边
} else {
l = mid + 1; // 将左界移向右边
}
}
printf("%lld\n", ans); // 打印答案
return 0;
}