Dijkstra和多层图 0

众所周知,Dijkstra经常拿来解决不带负权和环的单元最短路。我们先来看一下他的实现过程

(由于朴素版用的不多,我们直接上堆优化)

模板

cpp 复制代码
#include<bits/stdc++.h> 
#define mf(x,y) make_pair(x,y)//x距离,y节点 
using namespace std; 
int read(){
    int s=0,fl=1;char w=getchar();
    while(w>'9'||w<'0'){if(w=='-')fl=-1;w=getchar();}
    while(w<='9'&&w>='0'){s=s*10+(w^48);w=getchar();}
    return fl*s;
}
const int N=1000010;
int n,m,s,tot;
int head[N],ne[N],to[N],w[N];
void add(int x,int y,int ww){
    to[++tot]=y;
	ne[tot]=head[x];
	head[x]=tot;
	w[tot]=ww;  
}  
int d[N];
bool book[N];
void dj(int s){
    for(int i=1;i<=n;i++){
    	d[i]=2147483647;
	}
    d[s]=0;
    priority_queue<pair<int,int> >q;
    q.push(mf(0,s)); // 将源点及其距离为0的信息加入队列  
    while(!q.empty()){  
        int x=q.top().second;
		q.pop(); // 取出距离最小的节点  
        if(book[x]){
        	continue; // 如果节点已被访问过,则跳过 
		}
        book[x]=1; // 标记节点为已访问  
        for(int i=head[x];i;i=ne[i]){ // 遍历当前节点的所有邻接节点  
            int y=to[i]; // 邻接节点的编号
            if(d[y]>w[i]+d[x]){ // 如果通过当前节点可以找到更短的路径  
                d[y]=d[x]+w[i]; // 更新最短路径长度  
                q.push(mf(-d[y],y)); // 将邻接节点及其新的距离(取反后)加入队列  
           }
        }
    }
}
int main(){  
    n=read(),m=read(),s=read();
    for(int i=1,u,v,ww;i<=m;i++){
        u=read(),v=read(),ww=read();
        add(u,v,ww);
    }
    dj(s);
    for(int i=1;i<=n;i++){
    	printf("%d ",d[i]);
	}
	cout<<endl;
    return 0; 
}

题目1P4568 [JLOI2011] 飞行路线 - 洛谷

据题目可知,就是把每个城市看作一个节点,每条航线为一个边,机票费用为边权。要求在k此免费(也就是直接去一个城市而不用给机票费用)的情况下,从s飞到t

先考虑免费这个东西怎么处理。免费我们可以看作从节点1到节点2的边权为0。简单来说,我们可以建立层图

可以把这个多层图想象成「多层停车场」,每一层对应一个「权限使用次数」,帮你理解这段代码的作用:

  • 假设你要从起点开车到终点,停车场有 k+1 层(编号 0 到 k),每层对应 "使用了 j 次免费通行权限" 的状态(比如 j=0 表示一次没用到,j=1 表示用了 1 次,最多用到 j=k 次)。
  • 原始道路:每一层内部有普通道路,走一次要花 c 元(对应代码里同层边的权重 c)。
  • 免费通道:层与层之间有 "免费电梯",从第 j 层到第 j+1 层可以免费换乘(对应跨层边的权重 0),但每次换乘会消耗一次免费次数(所以最多到第 k 层)。

来看建图的代码:

cpp 复制代码
int main(){
	n=read();
	m=read();
	k=read();
	s=read();
	t=read();
	for(int i=1;i<=m;i++){
		int a,b,c;
		a=read();b=read();c=read();
		for(int j=0;j<=k;j++){
			add(a+j*n,b+j*n,c);
			add(b+j*n,a+j*n,c);
			if(j<k){
				add(a+j*n,b+(j+1)*n,0);
				add(b+j*n,a+(j+1)*n,0);
			}
		}
	}

a为起点,b为终点,c为权值。内层的for循环建立了k层图,也就是有多少免费次数就建多少层。因为第i层对应的是使用i次免费特权时图。

if嵌套外面的语句负责建立最基础的联通关系,提供基础信息

当j<k,代表免费次数没有用完,那么就在第j*n层和下一层建立一条边权为0的边,也就是当前层的每一个点都有免费通行到下一层的权利。

Dijkstra部分

当我们建完多层图后,就可以直接跑dijkstra了

cpp 复制代码
void dj(int s){
    memset(d,0x7f,sizeof(d));
    d[s]=0;
    priority_queue<pair<int,int> >q;
    q.push(mf(0,s));
    while(!q.empty()){  
        int x=q.top().second;
        int p=q.top().first;
		q.pop();
		if (p>d[x]) continue;
        if(book[x]){
        	continue;
		}
        book[x]=1;
        for(int i=head[x];i;i=ne[i]){  
            int y=to[i];
            if(d[y]>w[i]+d[x]){  
                d[y]=d[x]+w[i];
                q.push(mf(-d[y],y)); 
           }
        }
    }
}

没啥可说的

AC代码

cpp 复制代码
#include<bits/stdc++.h>
#define mf(x,y) make_pair(x,y)//x距离,y节点 

using namespace std;
const int N=5000100;
int read(){
    int s=0,fl=1;char w=getchar();
    while(w>'9'||w<'0'){if(w=='-')fl=-1;w=getchar();}
    while(w<='9'&&w>='0'){s=s*10+(w^48);w=getchar();}
    return fl*s;
}
void out(int x){
    if(x<0)putchar('-'),x=-x;
    if(x<10)putchar(x+'0');
    else out(x/10),putchar(x%10+'0');
}
int n,m,k,s,t;
int tot;
int head[N],ne[N],to[N],w[N];
void add(int x,int y,int ww){
    to[++tot]=y;
	ne[tot]=head[x];
	head[x]=tot;
	w[tot]=ww;  
}  
int d[N];
bool book[N];
void dj(int s){
    memset(d,0x7f,sizeof(d));
    d[s]=0;
    priority_queue<pair<int,int> >q;
    q.push(mf(0,s));
    while(!q.empty()){  
        int x=q.top().second;
        int p=q.top().first;
		q.pop();
		if (p>d[x]) continue;
        if(book[x]){
        	continue;
		}
        book[x]=1;
        for(int i=head[x];i;i=ne[i]){  
            int y=to[i];
            if(d[y]>w[i]+d[x]){  
                d[y]=d[x]+w[i];
                q.push(mf(-d[y],y)); 
           }
        }
    }
}


int main(){
	n=read();
	m=read();
	k=read();
	s=read();
	t=read();
	for(int i=1;i<=m;i++){
		int a,b,c;
		a=read();b=read();c=read();
		for(int j=0;j<=k;j++){
			add(a+j*n,b+j*n,c);
			add(b+j*n,a+j*n,c);
			if(j<k){
				add(a+j*n,b+(j+1)*n,0);
				add(b+j*n,a+(j+1)*n,0);
			}
		}
	}

	dj(s);
    int ans=1e18;
    for(int j=0;j<=k;j++){
        ans=min(ans,d[t+j*n]);
    }
    out(ans);
    return 0;
}

题目2

P1948 [USACO08JAN] Telephone Lines S - 洛谷

OK国际惯例,先来骗个分

不错,下面进入正题

据题可知,我们的目的只是把1号和n连起来。和上一道题一样,有k此免费连的机会。于是建图其实时差不多的。

cpp 复制代码
signed main(){
	n=read();
	p=read();
	k=read();
	for(int i=1;i<=p;i++){
		int x,y,v;
		x=read();y=read();v=read();
		for(int j=0;j<=k;j++){
			add(x+j*n,y+j*n,v);
			add(y+j*n,x+j*n,v);
			if(j<k){
				add(x+j*n,y+(j+1)*n,0);
				add(y+j*n,x+(j+1)*n,0);				
			}
		}
	}

Dijkstra部分

cpp 复制代码
void dj(int s){
	memset(d,0x7f,sizeof(d));
	d[s]=0;
	priority_queue<pair<int,int> >q;
	q.push(mf(0,s));
	while(!q.empty()){
		int x=q.top().second;
		int p=q.top().first;
		q.pop();
		if(book[x]) continue;
		book[x]=1;
		for(int i=head[x];i;i=ne[i]){
			int y=to[i];
			int np=max(-p,w[i]);
			if(d[y]>np){
				d[y]=np;
				q.push(mf(-d[y],y));
			}
		}
	}
}

和之前一题也差不多,但是不是求最短路,而是求当前的代价和连这条电线的最大值和y节点的最小值的最小值(?)

不过做这道题一定一定要注意数组大小,当时我就是数组开小了调了半天

AC代码

cpp 复制代码
#include<bits/stdc++.h>
#define mf(a,b) make_pair(a,b)
#define int long long
using namespace std;
const int N=5000010;
int read(){
    int s=0,fl=1;char w=getchar();
    while(w>'9'||w<'0'){if(w=='-')fl=-1;w=getchar();}
    while(w<='9'&&w>='0'){s=s*10+(w^48);w=getchar();}
    return fl*s;
}
void out(int x){
    if(x<0)putchar('-'),x=-x;
    if(x<10)putchar(x+'0');
    else out(x/10),putchar(x%10+'0');
}
int n,p,k;
int head[N],ne[N],to[N],w[N],d[N],book[N];
int tot;
void add(int x,int y,int v){
	to[++tot]=y;
	ne[tot]=head[x];
	head[x]=tot;
	w[tot]=v;
}
void dj(int s){
	memset(d,0x7f,sizeof(d));
	d[s]=0;
	priority_queue<pair<int,int> >q;
	q.push(mf(0,s));
	while(!q.empty()){
		int x=q.top().second;
		int p=q.top().first;
		q.pop();
		if(book[x]) continue;
		book[x]=1;
		for(int i=head[x];i;i=ne[i]){
			int y=to[i];
			int np=max(-p,w[i]);
			if(d[y]>np){
				d[y]=np;
				q.push(mf(-d[y],y));
			}
		}
	}
}
signed main(){
	n=read();
	p=read();
	k=read();
	for(int i=1;i<=p;i++){
		int x,y,v;
		x=read();y=read();v=read();
		for(int j=0;j<=k;j++){
			add(x+j*n,y+j*n,v);
			add(y+j*n,x+j*n,v);
			if(j<k){
				add(x+j*n,y+(j+1)*n,0);
				add(y+j*n,x+(j+1)*n,0);				
			}
		}
	}
	dj(1);
	int ans=2e9;
	for(int i=0;i<=k;i++){
		ans=min(ans,d[n+i*n]);
	}
	out(ans==2e9?-1:ans);
    return 0;
}