Dijkstra和多层图 0
众所周知,Dijkstra经常拿来解决不带负权和环的单元最短路。我们先来看一下他的实现过程
(由于朴素版用的不多,我们直接上堆优化)
模板
#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;
}
题目1 P4568 [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 层)。
来看建图的代码:
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了
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代码
#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此免费连的机会。于是建图其实时差不多的。
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部分
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代码
#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;
}