noip赛前20天冲刺集训 day2 ###寻找有向图中的最小疲惫路径###

发布时间 2023-10-12 08:33:49作者: Serein_Kar

T1 ###寻找有向图中的最小疲惫路径###

题目描述

有一张 n 个点 m 条边的有向图,每条边上有一个正整数边权,你要顺着图上的有向边从 1 号点走到 n 号点。

假设你经过的边边权依次为 (w_1, w_2, \dots, w_t),则你的疲惫程度为

\[\ f(w) =\max_{i=1}^{t} w_i \times i\,. \]

你需要找到最小疲惫程度的路径。

输入格式

第一行两个空格分隔的正整数 n, m,表示有向图的点数和边数。有向图的点用 1n 编号。

接下来 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 \(\leq n, m \leq\) 3 \(\times\) 105),(1 \(\leq\) w_i \(\leq\) 109),至少有一条从 1 号点到 n 号点的路径。

  • Subtask 1(20pts):(n, m \(\leq\) 20) , (\(w_i \leq\) 104)。
  • Subtask 2(20pts):(n, m \(\leq\) 100) , (\(w_i \leq\) 104)。
  • Subtask 3(20pts):(n, m \(\leq\) 2000)。
  • Subtask 4(20pts):(n, m \(\leq 5 \times\) 104)。
  • Subtask 5(20pts):(n, m \(\leq 3 \times\) 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;
}