目录
spfa
原理
- 确定一个起点,如果满足条件(能够使得路径缩小时),改变路径长度,并将处在起点周围且仍未纳入到队列中的点纳入到队列中,然后依次对队列中的点做相同的操作,直至队列为空。
前置知识
邻接表建图
idx
表示的是当前按顺序已经记录到第n条边。e[idx]
数组用来存放当前有向线段指向的下一端点。ne[idx]
表示的是同属某一起点的指向的下一条边的idx
位置h[aa]
表示的是以aa
为起点的最近新添加的起点。- w数组用来存放边权。
1.单向建图
const int N = 2e3+10,M =2e5+10;
int idx=0,e[M],ne[M],h[M];
double w[M];
void add(int aa,int bb,double cc)
{
e[idx]=bb,ne[idx]=h[aa],w[idx]=cc,h[aa]=idx++;
}
2.双向建图
- 当题目中有提示双向道路
const int N = 2e3+10,M =2e5+10;
int idx=0,e[M],ne[M],h[M];
double w[M];
void add(int aa,int bb,double cc)
{
e[idx]=bb,ne[idx]=h[aa],w[idx]=cc,h[aa]=idx++;
}
注意:记得在main函数里初始化h数组:memset(h,-1,sizeof(h));
~i
- 循环中i==-1的简化写法(ne数组各个元素的尽头是-1,相当于是再也找不到下一条边了)
沿着某一点转圈,遍历所有相邻边
for(int i=h[t];~i;i=ne[i])
{
}
0x3f
memset
用0x3f
填充数组的原因是0x3f
可以用来表示”无穷大“,且不是无穷大中的天花板(也就是说可以接受多个0x3f3f3f3f
相加且不爆数据范围的情况)。
spfa()特色
- dist数组
习题
裸题
1129. 热浪
#include<bits/stdc++.h>
using namespace std;
const int N =3000,M=20000;
int h[N],ne[M],w[M],e[M],idx=0;
void add(int a,int b,int c)
{
e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;
}
bool vis[N];
int spfa(int st,int ed)
{
int dist[M];
memset(vis,false,sizeof(vis));
memset(dist,0x3f,sizeof(dist));
queue<int > q;
q.push(st);
dist[st]=0;
vis[st]=true;
while(q.size())
{
int t = q.front();
q.pop();
vis[t]=false;
for(int i=h[t];~i;i=ne[i])
{
//cout<<1<<endl;
int j=e[i];
if(dist[j]>dist[t]+w[i])
{
dist[j]=dist[t]+w[i];
if(!vis[j])
{
vis[j]=true;
q.push(j);
}
}
}
}
return dist[ed];
}
int main()
{
int n,m,st,ed;
cin>>n>>m>>st>>ed;
memset(h,-1,sizeof(h));
for(int i = 0;i<m;i++)
{
int a,b,w;
cin>>a>>b>>w;
add(a,b,w),add(b,a,w);
} cout<<spfa(st,ed);
return 0;
}
多源最短路径
- 把每个点都扔进
spfa
里面,并在搜寻到的结果中找到最小值
1127. 香甜的黄油
农夫John发现了做出全威斯康辛州最甜的黄油的方法:糖。
把糖放在一片牧场上,他知道 N 只奶牛会过来舔它,这样就能做出能卖好价钱的超甜黄油。
当然,他将付出额外的费用在奶牛上。
农夫John很狡猾,就像以前的巴甫洛夫,他知道他可以训练这些奶牛,让它们在听到铃声时去一个特定的牧场。
他打算将糖放在那里然后下午发出铃声,以至他可以在晚上挤奶。
农夫John知道每只奶牛都在各自喜欢的牧场(一个牧场不一定只有一头牛)。
给出各头牛在的牧场和牧场间的路线,找出使所有牛到达的路程和最短的牧场(他将把糖放在那)。
数据保证至少存在一个牧场和所有牛所在的牧场连通。
#include<bits/stdc++.h>
using namespace std;
const int N = 3e3;
typedef long long ll;
int n,p,c;
vector<int > cow_pos;//奶牛位置
int h[N],ne[N],e[N],w[N],idx=0;
void add(int a,int b,int c)
{
e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
int spfa(int st)
{
int dist[N];
bool vis[N];
memset(dist,0x3f,sizeof(dist));
memset(vis,false,sizeof(vis));
queue<int > q;
q.push(st);
dist[st]=0;
vis[st]=true;
while(q.size())
{
int t=q.front();
q.pop();
vis[t]=false;
for(int i=h[t];~i;i=ne[i])
{
int j=e[i];
if(dist[j]>dist[t]+w[i])
{
dist[j]=dist[t]+w[i];//cout<<"jiancha "<<j<<" "<<dist[j]<<endl;
if(!vis[j])
{
vis[j]=true;
q.push(j);
}
}
}
}
ll res=0;
//for(int i=1;i<=p;i++)//只要接回所有奶牛,而不是到所有牧场的距离和
for(int i=0;i<cow_pos.size();i++)
{
//cout<<cow_pos[i]<<endl;
res+=dist[cow_pos[i]];
// cout<<i<<" "<<res<<endl;
}
return res;
}
int main()
{
cin>>n>>p>>c;
memset(h,-1,sizeof(h));
for(int i=0;i<n;i++)
{
int x;
cin>>x;
cow_pos.push_back(x);
}
for(int i=0;i<c;i++)
{
int aa,bb,cc;
cin>>aa>>bb>>cc;
add(aa,bb,cc);add(bb,aa,cc);
}
int ans=0x3f3f3f3f;
for(int i=1;i<=p;i++)
ans = min(ans,spfa(i));
cout<<ans;
return 0;
}
dist数组的变形:乘法dist
1128最小花费
在 n个人中,某些人的银行账号之间可以互相转账。
这些人之间转账的手续费各不相同。
给定这些人之间转账时需要从转账金额里扣除百分之几的手续费,请问 A最少需要多少钱使得转账后 BB 收到 100 元。
- 注意double初始化0x3f = 0.0004767
#include<bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
int n,m;
const int N = 2e3+10,M =2e5+10;
int idx=0,e[M],ne[M],h[M];
double w[M];
void add(int aa,int bb,double cc)
{
e[idx]=bb,ne[idx]=h[aa],w[idx]=cc,h[aa]=idx++;
}
double spfa(int st,int ed)
{
double dist[M];
bool used[N];
memset(dist,0x7f,sizeof(dist));//注意double初始化0x3f = 0.0004767
memset(used,false,sizeof(used));
queue<int > q;
q.push(st);
used[st]=true;
dist[st]=1;
while(q.size())
{
int t=q.front();
q.pop();
used[t]=false;
for(int i=h[t];~i;i=ne[i])
{
int j=e[i];
if(dist[j]<dist[t]*(1-w[i]))
{
dist[j]=dist[t]*(1-w[i]);
if(!used[j])
{
q.push(j);
used[j]=true;
}
}
}
}
return dist[ed];
}
int main()
{
memset(h,-1,sizeof(h));
cin>>n>>m;
for(int i=0;i<m;i++)
{
int x,y, z;
cin>>x>>y>>z;
add(x,y,1.0*z/100);add(y,x,1.0*z/100);
}
int st,ed;
cin>>st>>ed;
cout<<fixed<<setprecision(8)<<100/spfa(st,ed);
return 0;
}