【算法介绍】
SPFA实际上就是“队列优化的Bellman-Ford”,通过建立一个队列,每次取出队首进行松弛,再把有所调整的节点加入队伍
SPFA 主要用于解决有负权/有负环的问题,在稀疏图中效率较高,在稠密图或构造的网格图上会退化
【模板】
void spfa(int s) { for(int i=1;i<=totn;i++) dis[i]=inf; dis[s]=0; vis[s]=1; queue <int> q; q.push(s); while(!q.empty()) { int u=q.front(); q.pop(); for(int i=head[u];i;i=e[i].nxt) { int to=e[i].to; if(dis[to]>dis[u]+e[i].v) { dis[to]=dis[u]+e[i].v; if(!vis[to]) { vis[to]=1; q.push(to); } } } vis[u]=0; } }
【例题1】P4011 孤岛营救问题 (拯救大兵瑞恩)
【题意】
给定你一个N*M的矩阵迷宫,要从1,1走到N,M,每两个相邻的格子之间可能有墙/门,门一共分为P种,每种所用的钥匙相同,并给定一些坐标上,放有的钥匙号,现在请你求出从1,1到N,M的最短时间,若无法到达输出-1
【分析】
通过观察数据范围,可以发发现,P的值很小,而且如果不考虑钥匙的存在,这道题就是很裸的一个最短路问题,所以我们要想办法处理这个钥匙的问题
这里我们借助分层图的思想,把图分成层对应持有钥匙的不同状态,每一层按照你持有的钥匙构造地图,并且把当前层中有新钥匙可以捡的位置和有了这种钥匙后,你的新状态那一层的同样位置,连一条边权为0的边,然后就可以跑最短路了
时空复杂度均为
【代码】
#include<bits/stdc++.h>
using namespace std;
int n,m,p,k,s,totn;
const int maxn=105;
int dig[15][15],digit,door[maxn][maxn];
int cnt[15],has[15];
struct point
{
int x,y;
}pos[15][maxn];
const int inf=0x3f3f3f3f;
const int N=1024*105;
int head[N],tot;
struct edge
{
int to,nxt,v;
}e[N<<2];
void add(int x,int y,int z)
{
e[++tot].to=y; e[tot].nxt=head[x]; e[tot].v=z; head[x]=tot;
}
int dis[N],vis[N];
void spfa(int s)
{
for(int i=1;i<=totn;i++) dis[i]=inf;
dis[s]=0; vis[s]=1;
queue <int> q;
q.push(s);
while(!q.empty())
{
int u=q.front(); q.pop();
for(int i=head[u];i;i=e[i].nxt)
{
int to=e[i].to;
if(dis[to]>dis[u]+e[i].v)
{
dis[to]=dis[u]+e[i].v;
if(!vis[to])
{
vis[to]=1;
q.push(to);
}
}
}
vis[u]=0;
}
}
int main()
{
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
scanf("%d%d%d%d",&n,&m,&p,&k);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
dig[i][j]=++digit;
int x,y,z;
int a,b;
for(int i=1;i<=k;i++)
{
scanf("%d%d",&x,&y); a=dig[x][y];
scanf("%d%d",&x,&y); b=dig[x][y];
scanf("%d",&x);
if(x==0) x=-1;
door[a][b]=x; door[b][a]=x;
}
scanf("%d",&s);
for(int i=1;i<=s;i++)
{
scanf("%d%d%d",&x,&y,&z);
cnt[z]++;
pos[z][cnt[z]].x=x;
pos[z][cnt[z]].y=y;
}
int M=n*m,layer=1<<p;
totn=M*layer;
for(int k=0;k<layer;k++)
{
for(int j=1;j<=p;j++)
{
if(k&(1<<(j-1)))
has[j]=1;
else has[j]=0;
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
int x=dig[i][j],y=dig[i][j+1];
if(y && door[x][y]!=-1)
if(!door[x][y] || has[door[x][y]])
add(M*k+x,M*k+y,1),add(M*k+y,M*k+x,1);
y=dig[i+1][j];
if(y && door[x][y]!=-1)
if(!door[x][y] || has[door[x][y]])
add(M*k+x,M*k+y,1),add(M*k+y,M*k+x,1);
}
for(int i=1;i<=p;i++)
if(!has[i])
{
int t=k+(1<<(i-1));
for(int j=1;j<=cnt[i];j++)
{
int no=dig[pos[i][j].x][pos[i][j].y];
add(no+M*k,no+M*t,0);
}
}
}
spfa(1);
int ans=inf;
for(int i=0;i<layer;i++)
ans=min(ans,dis[i*M+dig[n][m]]);
if(ans==inf) printf("-1");
else printf("%d",ans);
return 0;
}
1. LLL优化:每次将入队结点距离和队内距离平均值比较,如果更大则插入至队尾。
Hack:向 1 连接一条权值巨大的边,这样LLL 就失效了。
2. SLF 优化:每次将入队结点距离和队首比较,如果更大则插入至队尾。
Hack:使用链套菊花的方法,在链上用几个并列在一起的小边权边就能欺骗算法多次进入菊花。
本文介绍了SPFA算法在解决有负权/负环最短路问题中的应用,以及如何处理孤岛营救问题。通过建立分层图,将钥匙的状态转换与地图结合,利用SPFA求解从起点到终点的最短时间。同时,文章讨论了LLL和SLF优化,并给出了针对这些优化的破解策略。
155

被折叠的 条评论
为什么被折叠?



