图论——分层图最短路
概述
分层图最短路,如:有 $k$ 次零代价通过一条路径,求总的最小花费。对于这种题目,我们可以采用 $DP$ 相关的思想,设 $\text{dis}_{i, j}$表示当前从起点 $i$ 号结点,使用了 $j$ 次免费通行权限后的最短路径。显然,$\text{dis}$ 数组可以这么转移:
$dis_i,_j = min(min(dis _v , _\text{j-1}if(j \leq k) ),min(dis _v,_\text{j}+w),dis _i,_j)$
其中$v$表是与$i$相邻的节点,$w$表示经过这条边的边权,特别地,如果$j > k$,那么$dis _v,_\text{j}=\infty$。
概念理解:分层图最短路往往是与$DP$思想结合的体现,它适用于求最短路性质的问题但加了额外限制,在考场上如果要想作对做这类题,必须先发现这道题隐藏条件和题目意思与最短路有关(不要小看这一步,因为往往最开始就没想到这样做,导致后面的结果是错误的),再思考怎样对题目进行图论建模。
例题
例$1$
简单来说,本题是在无向图上求出一条从$1$到$N$的路径,使路径上第$K + 1$大的边权尽量小。
可以仿照前面所说的方法,用$D[x,p]$表示从$1$号节点到达基站$x$,途中已经指定了$p$条电缆免费时,经过的路径最贵的电缆的话费最小是多少(也就是选择一条从$1$到$x$的路径,使路径上第$p+1$大的边权尽量少)。若有一条从$x$到$y$长度为$z$的无向边,则应该用$max(D[x,p],z)$更新$D[y,p]$的最小值,用$D[x,p]$更新$D[y,p+1]$的最小值。前者表示不在电缆$(x,y,z)$上使用免费升级服务,后者表示使用。
显然,我们刚才设计的状态转移是有后效性的(因为本题是按照动态规划的思想解决的,动态规划对状态空间的遍历构成一张有向无环图。注意一定是有向无环图!遍历顺序就是该有向无环图的一个拓扑序。 有向无向图中的节点对应问题中的“状态”,图中的边对应状态之间的“转移”,转移的选取就是动态规划中的“决策”。 在本题中,比如有三个点 $a,b,c$,构成一个环,那么用$dijkstra$更新就会发生当前更新的值不是最终的值,那么转移时就会出现问题。)。在有后效性时,一种解决方案是利用迭代思想,借助$SPFA$算法进行动态规划, 直至所有状态收敛(不能再更新)。
从最短路径问题的角度去理解,图中的节点也不仅限于“整数编号”,可以扩展到二维,用二元组$(x,p)$代表一个节点,从$(x,p)$到$(y,p)$有长度为$z$的边,从$(x,p)$到$(y,p+1)$有长度为$0$的边。$D[x,p]$表示起点$(1,0)$到节点$(x,p)$,路径上最长的边最短是多少。这是$N*K$个点,$P*K$条边的广义最短路径问题。对于非精心构造的数据,$SPFA$算法的时间复杂度为$O(tNP)$,其中$t$为常数,实际测试可以$AC$。本题也让我们进一步领会了动态规划与最短路的共通性。
代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,p,k;
const int N=1000000+10,M=10000000+10;
int head[N],to[M],nxt[M],cnt,w[M],dis[N];
struct node{
int val,pos;
bool operator >(const node &x)const{
return val>x.val;
}
};
void add(int u,int v,int f){
to[++cnt]=v;
w[cnt]=f;
nxt[cnt]=head[u];
head[u]=cnt;
return;
}
priority_queue<node,vector<node>,greater<node> >q;
bool v[N];
void dijkstra(){
memset(dis,0x3f,sizeof(dis));
dis[1]=0;
q.push(node{0,1});
while(!q.empty()){
node t=q.top();q.pop();
if(v[t.pos])continue;
v[t.pos]=1;
for(int i=head[t.pos];i;i=nxt[i]){
int v=to[i],z=max(dis[t.pos],w[i]);
if(dis[v]>z){
dis[v]=z;
q.push(node{dis[v],v});
}
}
}
return;
}
signed main(){
scanf("%lld %lld %lld",&n,&p,&k);
for(int i=1,x,y,z;i<=p;i++){
scanf("%lld %lld %lld",&x,&y,&z);
add(x,y,z);add(y,x,z);
for(int j=1;j<=k;j++){
add(x+(j-1)*n,y+j*n,0);
add(y+(j-1)*n,x+j*n,0);
add(x+j*n,y+j*n,z);
add(y+j*n,x+j*n,z);
}
}
dijkstra();
int ans=1e18;
for(int i=0;i<=k;i++){
ans=min(ans,dis[n+i*n]);
}
if(ans==1e18)printf("-1\n");
else printf("%lld\n",ans);
return 0;
}
例$2$
由于购买机票不需要花钱,所以肯定不会多次重复乘坐同样的航线或者多次访问到同一个城市。如果$k=0$本题就是最基础的最短路问题。但题目中说明了对有限条边设置为免费),可以使用分层图的方式,将图多复制$k$次,原编号为$i$的节点复制为编号$i+jn(1 \le j \le k)$的节点,然后对于原图存在的边,第$j$层和第$j+1$层的对应节点也需要连上,看起来就是相同的图上下堆叠起来。
代码如下
#include<bits/stdc++.h>
using namespace std;
const int N=1e4+50,M=2200050;
int head[N*12],cnt,nxt[M],to[M],w[M],n,m,k,s,t,dis[N*12];
void add(int u,int v,int f)
{
to[++cnt]=v;
nxt[cnt]=head[u];
w[cnt]=f;
head[u]=cnt;
}
struct node
{
int val,pos;
bool operator >(const node &x)const{
return val>x.val;
}
};
priority_queue<node,vector<node>,greater<node> >q;
bool b[N*12];
void dij()
{
memset(dis,0x3f,sizeof(dis));
dis[s]=0;
q.push(node{dis[s],s});
while(!q.empty())
{
node t=q.top();q.pop();
if(b[t.pos])continue;
b[t.pos]=1;
for(int i=head[t.pos];i;i=nxt[i])
{
int v=to[i];
if(dis[v]>dis[t.pos]+w[i])
{
dis[v]=dis[t.pos]+w[i];
q.push(node{dis[v],v});
}
}
}
return;
}
int main()
{
scanf("%d %d %d",&n,&m,&k);
scanf("%d %d",&s,&t);
for(int i=1,u,v,w;i<=m;i++)
{
scanf("%d %d %d",&u,&v,&w);
add(u,v,w);add(v,u,w);
for(int j=1;j<=k;j++)
{
add(u+j*n,v+j*n,w);
add(v+j*n,u+j*n,w);
add(u+j*n-n,v+j*n,0);
add(v+j*n-n,u+j*n,0);
}
}
dij();
int ans=0x3f3f3f3f;
for(int j=0;j<=k;j++)
if(dis[t+j*n]<ans)
ans=dis[t+j*n];
printf("%d\n",ans);
return 0;
}

浙公网安备 33010602011771号