众所周知,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;
}