#4754. 旅途

题目描述

B市可以由一个 $n$ 个点 $m$ 条边的带权无向图来描述。其中边 $e$ 的边权为 $w_e$ 。小A要从编号为 $1$ 的地方走到编号为 $n$ 的地方。对于一条选定的路径 $P$,小A要花的时间就是这条路径经过的边的总长度。问小A最少需要多少时间才能到?

当小A还在深思这个问题,担心自己不能准时到达的时候,一位长者来到他的面前。他可以用魔法,帮助小A减少他所需的时间。具体而言,对于一条路径 $p=\{e_1,e_2,\cdots,e_c\}$,小A最终需要的时间是中的前 $K$ 大的和。假如 $c<K$,则是这些边的总和。

当然小A是需要付出代价的,不同的 $K$,他需要 $w_{e_1},w_{e_2},\cdots,w_{e_c}$ 付出不同的代价。因此小A想知道当 $K$ 分别取 $1 \cdots n$ 的时候,他的最小时间耗费分别是多少。

题解

很妙的一道题。我们可以考虑到枚举哪条边是第 $k$ 大边,如果是暴力的话就可以把小于它的都设为 $0$ ,剩下的跑最短路,可惜这样是 $O(n^2mlogn)$ 的,那我们考虑把每条边的边权设为 $max(0,w_i-w_0)$ ,最后用 $dis(1,n)+k \times w_0$ 更新即可。显然这样可以取到最优值。要记得 $w_0$ 要多设成一次 $0$ 更新一遍答案,因为有可能一条路径不满 $k$ 条边,这时候就用最短路去更新即可。效率 $O(nmogn)$ 。

代码

#include <bits/stdc++.h>
#define LL long long
using namespace std;
const int N=6005;
struct O{int x;LL d;};
bool operator < (O A,O B){return A.d>B.d;}
int n,m,X[N],Y[N],Z[N],hd[N],V[N],nx[N],W[N],t;
LL s[N],d[N];priority_queue<O>q;
void add(int u,int v,int w){
    nx[++t]=hd[u];V[hd[u]=t]=v;W[t]=w;
}
LL work(int w){
    for (int j=1;j<=n;j++)
        hd[j]=0,d[j]=2e18;t=0;
    for (int j=1;j<=m;j++)
        add(X[j],Y[j],max(0,Z[j]-w)),
        add(Y[j],X[j],max(0,Z[j]-w));
    for (q.push((O){1,d[1]=0});!q.empty();){
        O u=q.top();q.pop();
        if (u.d!=d[u.x]) continue;
        for (int j=hd[u.x],v;j;j=nx[j])
            if (d[v=V[j]]>u.d+W[j])
                q.push((O){v,d[v]=u.d+W[j]});
    }
    return d[n];
}
int main(){
    cin>>n>>m;
    for (int i=1;i<=n;i++) s[i]=2e18;
    for (int i=1;i<=m;i++)
        scanf("%d%d%d",&X[i],&Y[i],&Z[i]);
    for (int i=1;i<=m;i++){
        LL v=work(Z[i]);
        for (int j=1;j<=n;j++)
            s[j]=min(s[j],v+1ll*Z[i]*j);
    }
    work(0);
    for (int i=n;i;i--)
        printf("%lld\n",min(d[n],s[i]));
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/xjqxjq/p/12535822.html