题解 P2349 【金字塔】

· · 题解

在机房昏昏欲睡,随手点了下随机跳题看看会有什么神奇黑题,于是就有了这篇题解。

第一眼感觉题目非常眼熟,在看到如此之小的数据范围和蓝题标签,当场瞳孔地震,实在是水。

题解正式开始

并不会高大上的 A^*,将此题模型作为最短路变形来求解的。

如果准备好了以上食材,就接着往下看吧。

设没有打开宝箱时 1n 的一条路径 T_i 的长度(用时)为 d_i

而有浓烟出现后的最短路径长度为:S=min{ d_i+w_j } ,其中 w_j 是路径 T_i 上一条边的长度。

最坏情况指逃出时间最长,所以 w_j 应该是 路径 T_i 中最长的一条边。

那么有浓烟时,在最坏的情况下所用的时间最少的路径长度应为 S=min{d_i+w_{i_{max}} }

答案就应该为一条【最长边的长度尽量小】的最短路的长度。

最大值最小这个经典问题自然用二分答案来求解。二分路径中最长边的大小,来跑最短路,所有大于二分值的边都不经过。如果当前二分值能够更新答案,说明可能存在更短的边再次更新答案,就让 r=mid,反之就让 l=mid+1

若有多条起点为 $1$ ,终点为 $n$ 的**最短路径**,长度为 $D$ , 其中第 $i $ 条路径中最长的一条边长度为 $w_i$,我们应该选择 min {$\,w_i\,$}所在的那条路径,否则总存在一个 $w_i$ 使得 $w_i+D<res$ 。 ------------ **before the code** 本题解代码只是为了便于理解,有一些地方的实现可能与题解并不相同 ~~(因为我懒得改了)~~ 在代码中进行了必要的注释,如果有疑惑欢迎私信或评论。 ```cpp #include<cstdio> #include<cstring> #include<iostream> #include<queue> using namespace std; const int maxn=110; const int maxm=4001; int n,m,tot,maxx,res=0x3f3f3f3f; int lin[maxn],to[maxm],ne[maxm]; int va[maxm],from[maxm]; void add(int u,int v,int k) {//存图 to[++tot]=v; ne[tot]=lin[u]; lin[u]=tot; va[tot]=k; from[tot]=u; } bool check(int w) { int vis[maxn],dis[maxn],max1=0; int pre[maxn]; memset(dis,0x3f,sizeof(dis)); memset(vis,0,sizeof(vis)); dis[1]=0; pre[1]=0; queue<int>q; vis[1]=1; q.push(1);//SPFA的准备工作 while(!q.empty()) { int x=q.front(); q.pop(); vis[x]=0; for(int i=lin[x];i;i=ne[i]) { int t=to[i]; if(va[i]<=w)//最长边不大于二分值 { if(dis[t]>dis[x]+va[i]) { pre[t]=i; dis[t]=dis[x]+va[i]; if(!vis[t]) q.push(t); } } } } if(dis[n]<=res)//可以更新答案 { for(int i=pre[n];i;i=pre[from[i]])//找到最长边 { max1=max(max1,va[i]); } res=dis[n]+max1;//更新答案 return 1; } return 0; } int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=m;i++) { int a,b,c; scanf("%d%d%d",&a,&b,&c); add(a,b,c); add(b,a,c); maxx=max(maxx,c); } //二分最长边的边权 int l=1,r=maxx+10;//配合二分写法,将值域进行扩大 while(l<r) { int mid=(l+r)>>1;//注意这种取法mid取不到r if(check(mid))//可以更新答案 r=mid; else//不可以更新答案 l=mid+1; } printf("%d",res);//输出结果 return 0; } ``` ### **See Ya!**