【XSY3888】传送门(最短路树,可并堆,dp)
题面
题解
假设 到 路径上一条边 被删掉之后最短路会如何变化。
建出以 为根的最短路树,如果 不在树上,那么我们直接从 沿着最短路树走到 即可。
否则,如果 在最短路树上,那么 一定是 的父亲。那么我们需要从 走到 子树内的任意一个点 ,然后再从 经过一条非树边跳到一个在 子树外的点 ,然后再沿最短路树走到 。
类似这样:
(其中黑色虚线边表示的是非树边,红叉边表示被删掉的边,蓝色表示路径)
设 表示最短路树上 到 的路径长。考虑如何维护上面蓝色路径的最小值。把这段路程表示出来:
那么我们直接维护 的最小值即可。
对于每一个点 ,用堆维护其子树内 的最小值。
合并的时候用左偏树即可。
最多合并 次( 条边),所以维护这个的过程时间不会超过 。
不妨设 表示删掉边 之后到达 的最小值。( 通过上面的过程求出)
令 表示 时的答案,用 更新 时的转移为:
每次找到 值最小的点来更新其它的点的 值即可,类似 dijkstra。
时间复杂度 。
代码如下:
#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
*/
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现