图论——分层图最短路

概述

分层图最短路,如:有 \(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\)

Telephone Lines

简单来说,本题是在无向图上求出一条从\(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;
}
posted @ 2023-12-19 19:18  CQWYB  阅读(328)  评论(0编辑  收藏  举报