【XSY3888】传送门(最短路树,可并堆,dp)

题面

在这里插入图片描述

题解

假设 S S S T T T 路径上一条边 ( u , v ) (u,v) (u,v) 被删掉之后最短路会如何变化。

建出以 T T T 为根的最短路树,如果 ( u , v ) (u,v) (u,v) 不在树上,那么我们直接从 u u u 沿着最短路树走到 T T T 即可。

否则,如果 ( u , v ) (u,v) (u,v) 在最短路树上,那么 v v v 一定是 u u u 的父亲。那么我们需要从 u u u 走到 u u u 子树内的任意一个点 x x x,然后再从 x x x 经过一条非树边跳到一个在 u u u 子树外的点 y y y,然后再沿最短路树走到 T T T

类似这样:

在这里插入图片描述

(其中黑色虚线边表示的是非树边,红叉边表示被删掉的边,蓝色表示路径)

d i s u dis_u disu 表示最短路树上 T T T u u u 的路径长。考虑如何维护上面蓝色路径的最小值。把这段路程表示出来:

d i s x − d i s u + w ( x , y ) + d i s y dis_x-dis_u+w_{(x,y)}+dis_y disxdisu+w(x,y)+disy

那么我们直接维护 d i s x + w ( x , y ) + d i s y dis_x+w_{(x,y)}+dis_y disx+w(x,y)+disy 的最小值即可。

对于每一个点 u u u,用堆维护其子树内 d i s x + w ( x , y ) + d i s y dis_x+w_{(x,y)}+dis_y disx+w(x,y)+disy 的最小值。

合并的时候用左偏树即可。

最多合并 n − 1 n-1 n1 次( n − 1 n-1 n1 条边),所以维护这个的过程时间不会超过 O ( n log ⁡ n ) O(n\log n) O(nlogn)

不妨设 g u g_u gu 表示删掉边 ( u , fa u ) (u,\textit{fa}_u) (u,fau) 之后到达 T T T 的最小值。( g u g_u gu 通过上面的过程求出)

d p u dp_u dpu 表示 S = u S=u S=u 时的答案,用 v v v 更新 u u u 时的转移为:

d p u = min ⁡ ( u , v ) ( max ⁡ ( d p v + w ( u , v ) , { g u v = fa u d i s u v ≠ fa u ) ) dp_u =\min_{(u,v)}\left(\max\left(dp_v+w_{(u, v)}, \begin{cases}g_u&v=\textit{fa}_u\\dis_u&v\neq \textit{fa}_u\end{cases}\right)\right) dpu=(u,v)min(max(dpv+w(u,v),{gudisuv=fauv=fau))

每次找到 d p dp dp 值最小的点来更新其它的点的 d p dp dp 值即可,类似 dijkstra。

时间复杂度 O ( ( n + m ) log ⁡ n ) O((n+m)\log n) O((n+m)logn)

代码如下:

#include<bits/stdc++.h>
 
#define N 100010
#define M 200010
#define intree(v,u) (dfn[u]<=dfn[v]&&dfn[v]<=dfn[u]+size[u]-1)//判断是否在子树内
 
using namespace std;
 
struct data
{
    int u,s;
    data(){};
    data(int a,int b){u=a,s=b;}
    bool operator < (const data &a) const
    {
        return s>a.s;
    }
};
 
struct Heap//左偏树
{
    #define lc(u) ch[u][0]
    #define rc(u) ch[u][1]
    int node,ch[M][2],to[M],dis[M],val[M];
    int top,rubbish[M];
    int newnode(int y,int v)
    {
        int now;
        if(top)
        {
            now=rubbish[top--];
            lc(now)=rc(now)=dis[now]=0;
        }
        else now=++node;
        to[now]=y,val[now]=v;
        return now;
    }
    int merge(int x,int y)
    {
        if(!x||!y) return x+y;
        if(val[x]>val[y]) swap(x,y);
        rc(x)=merge(rc(x),y);
        if(dis[rc(x)]>dis[lc(x)]) swap(lc(x),rc(x));
        dis[x]=dis[rc(x)]+1;
        return x;
    }
    int pop(int u)
    {
        rubbish[++top]=u;
        return merge(lc(u),rc(u));
    }
}heap;
 
int n,m;
int cnt,head[N],nxt[M<<1],to[M<<1],w[M<<1];
int dis[N],fa[N];
int idx,dfn[N],size[N];
int root[N];
int dp[N],g[N];
bool vis[N];
 
vector<int>e[N];
priority_queue<data>q;
 
void adde(int u,int v,int wi)
{
    to[++cnt]=v;
    w[cnt]=wi;
    nxt[cnt]=head[u];
    head[u]=cnt;
}
 
void dijkstra()
{
    memset(dis,127,sizeof(dis));
    q.push(data(n,0));
    dis[n]=0;
    while(!q.empty())
    {
        data now=q.top();
        q.pop();
        if(vis[now.u]) continue;
        vis[now.u]=1;
        for(int i=head[now.u];i;i=nxt[i])
        {
            int v=to[i];
            if(dis[now.u]+w[i]<dis[v])
            {
                dis[v]=dis[now.u]+w[i];
                fa[v]=now.u;
                q.push(data(v,dis[v]));
            }
        }
    }
}
 
void dfs(int u)
{
    dfn[u]=++idx;
    size[u]=1;
    for(int i=0,siz=e[u].size();i<siz;i++)
    {
        int v=e[u][i];
        dfs(v);
        size[u]+=size[v];
        root[u]=heap.merge(root[u],root[v]);
    }
    for(int i=head[u];i;i=nxt[i])
    {
        int v=to[i];
        if(v==fa[u]||intree(v,u)) continue;
        root[u]=heap.merge(root[u],heap.newnode(v,dis[u]+w[i]+dis[v]));
    }
    while(root[u]&&intree(heap.to[root[u]],u))
        root[u]=heap.pop(root[u]);
    if(root[u]) g[u]=heap.val[root[u]]-dis[u];
}
 
void DP()
{
    memset(vis,0,sizeof(vis));
    memset(dp,127,sizeof(dp));
    q.push(data(n,0));
    dp[n]=0;
    while(!q.empty())
    {
        data now=q.top();
        q.pop();
        if(vis[now.u]) continue;
        vis[now.u]=1;
        for(int i=head[now.u];i;i=nxt[i])
        {
            int v=to[i];
            if(max(dp[now.u]+w[i],now.u==fa[v]?g[v]:dis[v])<dp[v])
            {
                dp[v]=max(dp[now.u]+w[i],now.u==fa[v]?g[v]:dis[v]);
                q.push(data(v,dp[v]));
            }
        }
    }
}
 
int main()
{
    memset(g,127,sizeof(g));
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        adde(u,v,w),adde(v,u,w);
    }
    dijkstra();
    for(int i=1;i<n;i++)
        e[fa[i]].push_back(i);
    dfs(n);
//  puts("I didn't MLE!!!");
//  return 0;
    DP();
    if(dp[1]!=dp[0]) printf("%d\n",dp[1]);
    else puts("-1");
    return 0;
}
/*
5 10
1 3 10
1 5 4
3 4 6
5 2 10
1 2 6
4 5 2
2 3 2
4 1 5
4 2 6
5 3 3
*/
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值