传送门(最短路树+可并堆)

Description

有一张n个点m条边的无向图,求删去任意一条边后,从S到T的最短距离的最大值

n, m ≤ 2 × 1 0 5 2 \times 10^5 2×105

Solution

这道题是[USACO09JAN]Safe Travel的变形,然后这是题解

Safe Travel这道题的普遍做法是并查集树剖,但学长的PDF里提到的是题解讲的可并堆做法,所以我就没采用前两种

然后讲回传送门这题,
首先考虑怎么求删掉一条边后相邻两个点到 T 的最短距离。建出最短路树,如果删掉的不是连向父亲的边,则最短路不变,否则就和Safe Travel一样了

那么怎么求出最终答案呢?可以在图上DP一下

对每个点 u,记 d(u) 表示 u 到 T 的最短路,e(u) 表示删掉它和最短路树上父亲的边后的最短路。令 dp(u) 表示 S = u 时的答案。每次找到 dp 值最小的点来更新其它的点的 dp 值即可。用 u 更新 v 时的转移为 dp(v) =min { max(dp(u) + w(u, v), u == parent v?e(v) : d(v)) }。

Code

//一定不能把S,T反过来,不然就全WA了 
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
typedef long long ll;
typedef pair<ll,int> pr;
const int inf=0x7fffffff;
const int N=2e5+5;
const int M=2e5+5;
struct Edge{
    int v,w,nxt,tr;
}edge[M<<1];
int head[N],cnt,vis[N],pre[N];ll dis[N];
priority_queue<pr,vector<pr> ,greater<pr> > q;
int siz[N],dfn[N],ind,parent[N];ll e[N],dp[N];
struct Node{
    int to,ls,rs,fa,dist;ll val;
}t[M*20];
int tot,rt[N];
int n,m;
void add_edge(int u,int v,int w){
    edge[++cnt].v=v;
    edge[cnt].w=w;
    edge[cnt].nxt=head[u];
    head[u]=cnt;
}
void dijskra(int s){
    for(int i=1;i<=n;i++) dis[i]=inf;
    memset(vis,0,sizeof(vis));
    dis[s]=0;
    q.push(make_pair(0,s));
    while(!q.empty()){
        pr tmp=q.top();
        q.pop();
        int u=tmp.second;
        if(vis[u]) continue;
        vis[u]=1;
        for(int i=head[u];i;i=edge[i].nxt){
            int v=edge[i].v,w=edge[i].w;
            if(dis[v]>dis[u]+1ll*w){
                dis[v]=dis[u]+1ll*w;
                pre[v]=i;
                q.push(pr(dis[v],v));
            }
        }
    }
}
void build(){//建最短路树 
    for(int i=1;i<=n;i++)
        if(pre[i]) edge[pre[i]].tr=1;
}
int merge(int a,int b){
    if(!a||!b) return a+b;
    if(t[a].val>t[b].val) swap(a,b);
    t[a].rs=merge(t[a].rs,b);
    t[t[a].rs].fa=a;
    if(t[t[a].ls].dist<t[t[a].rs].dist) swap(t[a].ls,t[a].rs);
    t[a].dist=t[t[a].rs].dist+1;
    return a;
}
int pop(int a){
    return merge(t[a].ls,t[a].rs);
}
bool check(int u,int v){
    return dfn[v]>=dfn[u]&&dfn[v]<=dfn[u]+siz[u]-1;
}
void dfs(int u,int fa){
    dfn[u]=++ind;
    siz[u]=1;
    for(int i=head[u];i;i=edge[i].nxt){
        int v=edge[i].v;
        if(v==fa||!edge[i].tr) continue;
        parent[v]=u;
        dfs(v,u);
        rt[u]=merge(rt[u],rt[v]);
        siz[u]+=siz[v];
    }
    for(int i=head[u];i;i=edge[i].nxt){
        int v=edge[i].v;
        if(v==fa||edge[i].tr) continue;//(u,fa)也是树边,不考虑 
        t[++tot]=(Node){v,0,0,tot,0,dis[u]+dis[v]+edge[i].w};
        rt[u]=merge(rt[u],tot); 
    }
    while(check(u,t[rt[u]].to)) rt[u]=pop(rt[u]);
    e[u]=rt[u]?t[rt[u]].val-dis[u]:inf;
}
void get_ans(int s){
    for(int i=1;i<=n;i++) dp[i]=inf;
    memset(vis,0,sizeof(vis));
    dp[s]=0;
    q.push(make_pair(0,s));
    while(!q.empty()){
        pr tmp=q.top();
        q.pop();
        int u=tmp.second;
        if(vis[u]) continue;
        vis[u]=1;
        for(int i=head[u];i;i=edge[i].nxt){
            int v=edge[i].v,w=edge[i].w;
            int t=max(dp[u]+w,parent[v]==u?e[v]:dis[v]);
            if(dp[v]>t){
                dp[v]=t;
                q.push(pr(dp[v],v));
            }
        }
    }
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        add_edge(u,v,w);
        add_edge(v,u,w);
    }
    dijskra(n);
    build();
    tot=n;
    dfs(n,0);
    get_ans(n);
    if(dp[1]==inf) printf("%d\n",-1); 
    else printf("%lld\n",dp[1]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值