Dijkstra、SPFA、Floyd、SPFA求负环
前言
非常非常经典的最短路问题,本文用了三种算法:Dijkstra、SPFA、Floyd
Dijkstra使用贪心思想,比较快,只可解决单源非负权值的最短路问题
SPFA是Bellman-Ford的队列优化,速度一般(有时候会超过Dijkstra),可以解决单源最短路问题
Floyd速度比较慢,可以解决点点最短路问题(听说如果不会Floyd可以外层套一层循环做Dijkstra也可解决 = =|| )
鉴于SPFA实现比较简单,因此个人目前习惯用SPFA解决问题(希望出题人不要卡我T^T)
Til the Cows Come Home - POJ - 2387
题意
给定邻接链表求1到n的最短路
思路
模板题
Dijkstra做法:
#include <cstdio>
#include <iostream>
#include <cstring>
#include <queue>
#include <algorithm>
using namespace std;
const int N = 2e5 + 15;
const int inf = 0x3f3f3f3f;
struct node{
int val, u;
node(int pv, int pu): val(pv), u(pu) {}
bool operator < (const node& b) const { return val > b.val; }
};
struct edge{
int v, val, next;
};
edge e[N];
int head[N];
int tot;
int d[N];
priority_queue<node> que;
inline void init(){
memset(d, 0x3f, sizeof(d));
memset(head, -1, sizeof(head));
tot = 0;
d[1] = 0;
}
inline void addEdge(int u, int v, int val){
e[tot].v = v;
e[tot].val = val;
e[tot].next = head[u];
head[u] = tot++;
}
void dijkstra(){
que.push(node(0, 1));
while(!que.empty()){
int val = que.top().val;
int u = que.top().u;
que.pop();
if(d[u] < val) continue;
for(int i = head[u]; ~i; i = e[i].next){
int v = e[i].v;
if(d[v] > d[u] + e[i].val){
d[v] = d[u] + e[i].val;
que.push(node(d[v], v));
}
}
}
}
int main(){
int n, m;
while(~scanf("%d%d", &n, &m)){
init();
while(n--){
int u, v, val;
scanf("%d%d%d", &u, &v, &val);
addEdge(u, v, val);
addEdge(v, u, val);
}
dijkstra();
printf("%d\n", d[m]);
}
}
SPFA做法:
#include <cstdio>
#include <iostream>
#include <cstring>
#include <queue>
#include <algorithm>
using namespace std;
const int N = 2e5 + 15;
const int inf = 0x3f3f3f3f;
struct edge{
int v, val, next;
};
edge e[N];
int head[N];
int tot;
int d[N];
bool inque[N];
queue<int> que;
inline void init(){
memset(inque, false, sizeof(inque));
memset(d, 0x3f, sizeof(d));
memset(head, -1, sizeof(head));
tot = 0;
d[1] = 0;
}
inline void addEdge(int u, int v, int val){
e[tot].v = v;
e[tot].val = val;
e[tot].next = head[u];
head[u] = tot++;
}
void spfa(){
que.push(1);
inque[1] = true;
while(!que.empty()){
int u = que.front();
inque[u] = false;
que.pop();
for(int i = head[u]; ~i; i = e[i].next){
int v = e[i].v;
if(d[v] > d[u] + e[i].val){
d[v] = d[u] + e[i].val;
if(!inque[v]){
que.push(v);
inque[v] = true;
}
}
}
}
}
int main(){
int n, m;
while(~scanf("%d%d", &n, &m)){
init();
while(n--){
int u, v, val;
scanf("%d%d%d", &u, &v, &val);
addEdge(u, v, val);
addEdge(v, u, val);
}
spfa();
printf("%d\n", d[m]);
}
}
最短路径·二:Floyd算法 - HihoCoder - 1089
思路
教程 + 模板题
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int N = 101;
int d[N][N];
inline void init(int n){
memset(d[0], 0x3f, sizeof(d));
for(int i = 1; i <= n; i++) d[i][i] = 0;
}
void floyd(int n){
for(int k = 1; k <= n; k++){
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++){
d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}
}
}
}
int main(){
int n, m;
while(~scanf("%d%d", &n, &m)){
init(n);
while(m--){
int u, v, val;
scanf("%d%d%d", &u, &v, &val);
d[u][v] = d[v][u] = min(val, d[u][v]);
}
floyd(n);
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++){
printf("%d", d[i][j]);
if(j < n) putchar(' ');
}
puts("");
}
}
}
Frogger - POJ - 2253
题意
求源点到终点的路径中最大权值最小的那条路径中的权值最大值
思路
个人感觉不像单源最短路径,更像是最小生成树
首先算不同点的距离,填充邻接矩阵
然后参照最小生成树,将mincost定为当前维护的最大值,不断更新不同点的mincost,最后输出终点的mincost即可
#include <iostream>
#include <cstdio>
#include <queue>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
const int N = 505;
typedef long long ll;
const int inf = 0x3f3f3f3f;
struct node{
double cost;
int u;
node(double pc, int pu): cost(pc), u(pu) {}
bool operator < (const node& b) const { return cost > b.cost; }
};
double G[N][N];
double mincost[N];
int used[N];
int d[N][2];
priority_queue<node> que;
inline void init(){
for(int i = 0; i < N; i++) mincost[i] = inf;
memset(used, false, sizeof(used));
}
void prim(int n){
double ans = 0;
mincost[0] = 0;
que.push(node(0, 0));
while(!que.empty()){
int u = que.top().u;
double cost = que.top().cost;
que.pop();
if(used[u] || mincost[u] < cost) continue;
used[u] = true;
mincost[u] = cost;
ans = max(cost, ans);
for(int v = 0; v < n; v++){
if(u == v || G[u][v] == inf) continue;
if(!used[v] && mincost[v] > max(G[u][v], ans)){
mincost[v] = max(G[u][v], ans);
que.push(node(mincost[v], v));
}
}
}
}
int main(){
int n;
int caseno = 1;
while(scanf("%d", &n) && n){
init();
for(int i = 0; i < n; i++) scanf("%d%d", &d[i][0], &d[i][1]);
for(int i = 0; i < n; i++){
for(int j = 0; j < n; j++){
if(i == j){
G[i][j] = 0;
}else{
double dis = sqrt((d[i][0] - d[j][0]) * (d[i][0] - d[j][0]) + (d[i][1] - d[j][1]) * (d[i][1] - d[j][1]));
G[i][j] = G[j][i] = dis;
}
}
}
prim(n);
if(caseno > 1) puts("");
printf("Scenario #%d\n", caseno++);
printf("Frog Distance = %.3f\n", mincost[1]);
}
}
Silver Cow Party - POJ - 3268
题意
给定有向图,求任意点作为出发点,其到目标点,再从目标点回到对应的出发点的最小总路径中的最大值
思路
先从目标点跑最短路,可以计算出目标点到任意点的最短路径,再把图倒过来再跑一遍最短路,可以计算出任意点到目标点的最短路径,两者对应相加取最大就是答案了
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 1005 + 15;
typedef long long ll;
const int inf = 0x3f3f3f3f;
struct edge{ int v, val, next; };
int headt[N], head[N];
edge e[N*N], et[N*N];
int d[N], dt[N];
int tot;
queue<int> que;
inline void init(){
memset(head, -1, sizeof(head));
memset(headt, -1, sizeof(headt));
memset(d, 0x3f, sizeof(d));
memset(dt, 0x3f, sizeof(dt));
tot = 0;
}
inline void addEdge(int u, int v, int val){
e[tot].v = v;
e[tot].val = val;
e[tot].next = head[u];
head[u] = tot;
et[tot].v = u;
et[tot].val = val;
et[tot].next = headt[v];
headt[v] = tot++;
}
void spfa(int s, int n){
que.push(s);
d[s] = 0;
while(!que.empty()){
int u = que.front();
que.pop();
for(int i = head[u]; ~i; i = e[i].next){
int v = e[i].v;
if(d[v] > d[u] + e[i].val){
d[v] = d[u] + e[i].val;
que.push(v);
}
}
}
que.push(s);
dt[s] = 0;
while(!que.empty()){
int u = que.front();
que.pop();
for(int i = headt[u]; ~i; i = et[i].next){
int v = et[i].v;
if(dt[v] > dt[u] + et[i].val){
dt[v] = dt[u] + et[i].val;
que.push(v);
}
}
}
}
int main(){
int n, m, s;
while(~scanf("%d%d%d", &n, &m, &s)){
init();
while(m--){
int u, v, val;
scanf("%d%d%d", &u, &v, &val);
addEdge(u, v, val);
}
spfa(s, n);
int ans = 0;
for(int i = 1; i <= n; i++){
ans = max(ans, d[i] + dt[i]);
}
printf("%d\n", ans);
}
}
Currency Exchange - POJ - 1860
题意
有n种货币,m种兑换方式,现在Nick持有第s种货币,其金额为w,在m种兑换方式中,每一种的形式都是将a货币兑换成b货币,需要c1元手续费,其比率为r1;将b货币兑换为a货币,需要c2元手续费,其比率为r2。现在问Nick能否通过不断地兑换,最终重新兑换回原有货币,其金额增加
思路
实际上就是找一个权值为正的环,所以用到SPFA判负环的思想
注意这里边的权值应为 (a货币 - 手续费)*(1 + 比率)
#include <cstdio>
#include <iostream>
#include <cstring>
#include <queue>
#include <algorithm>
#include <map>
using namespace std;
const int N = 1005;
const int inf = 0x3f3f3f3f;
struct edge{
int v, next;
double commision, rate;
};
edge e[N << 1];
int head[N];
int tot;
double d[N];
bool inque[N];
queue<int> que;
inline void init(){
memset(head, -1, sizeof(head));
memset(inque, false, sizeof(inque));
memset(d, 0, sizeof(d));
tot = 0;
}
inline void addEdge(int u, int v, double commision, double rate){
e[tot].v = v;
e[tot].rate = rate;
e[tot].commision = commision;
e[tot].next = head[u];
head[u] = tot++;
}
bool spfa(int s, double base){
d[s] = base;
que.push(s);
while(!que.empty()){
int u = que.front();
que.pop();
inque[u] = false;
for(int i = head[u]; ~i; i = e[i].next){
int v = e[i].v;
if(d[v] < (d[u] - e[i].commision) * e[i].rate){
d[v] = (d[u] - e[i].commision) * e[i].rate;
if(!inque[v]){
que.push(v);
inque[v] = true;
}
}
if(d[s] > base) return true;
}
}
return false;
}
int main(){
int n, m, s;
double base;
while(~scanf("%d%d%d%lf", &n, &m, &s, &base)){
init();
while(m--){
int u, v;
double commision1, rate1, commision2, rate2;
scanf("%d%d%lf%lf%lf%lf", &u, &v, &rate1, &commision1, &rate2, &commision2);
addEdge(u, v, commision1, rate1);
addEdge(v, u, commision2, rate2);
}
puts(spfa(s, base) ? "YES" : "NO");
}
}
Subway - POJ - 2502
题意
你现在要从家到学校去,你的步行速度是10km/h,地铁的速度为40km/h,现在给定你家的坐标和学校的坐标,与地铁线路,及其每条线路对应站点的坐标(上述坐标均以m为单位),现在问选择怎样的方案能够最快时间内到达学校,求这个最小时间(以min为单位)
思路
这题就是建模挺烦,首先单位换算,然后边的权值就可以以min为单位了
然后构图,点点之间加一条步行所花时间的边,同线路相邻的地铁站点间加一条坐地铁所花时间的边
最后跑最短路,得到答案
#include <cstdio>
#include <iostream>
#include <cstring>
#include <queue>
#include <vector>
#include <algorithm>
#include <cmath>
using namespace std;
const int N = 205;
const double inf = 1e8;
struct node{
int id;
double x, y;
node(int pid, double px, double py): id(pid), x(px), y(py) {}
};
struct edge{
double val;
int v, next;
};
edge e[N*N << 2];
int tot;
int head[N];
double d[N];
queue<int> que;
bool inque[N];
vector<node> vec[N];
vector<node> nd;
inline void init(){
for(int i = 0; i < N; i++) vec[i].clear();
nd.clear();
for(int i = 0; i < N; i++) d[i] = inf;
memset(head, -1, sizeof(head));
memset(inque, false, sizeof(inque));
tot = 0;
}
inline void addEdge(int u, int v, double val){
e[tot].v = v;
e[tot].val = val;
e[tot].next = head[u];
head[u] = tot++;
}
inline double getDis(double x1, double y1, double x2, double y2){
return sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
}
void spfa(){
d[1] = 0;
inque[1] = true;
que.push(1);
while(!que.empty()){
int u = que.front();
que.pop();
inque[u] = false;
for(int i = head[u]; ~i; i = e[i].next){
int v = e[i].v;
if(d[v] > d[u] + e[i].val){
d[v] = d[u] + e[i].val;
if(!inque[v]){
inque[v] = true;
que.push(v);
}
}
}
}
}
int main(){
ios::sync_with_stdio(false);
double sx, sy, tx, ty;
while(cin >> sx >> sy >> tx >> ty){
init();
nd.push_back(node(1, sx, sy));
nd.push_back(node(2, tx, ty));
int line = 1;
int id = 3;
double x, y;
while(cin >> x >> y){
if(x != -1 && y != -1){
node tmp(id, x, y);
vec[line].push_back(tmp);
nd.push_back(tmp);
id++;
}else{
line++;
}
}
line--;
for(int i = 0; i < nd.size(); i++){
for(int j = i + 1; j < nd.size(); j++){
double dis = getDis(nd[i].x, nd[i].y, nd[j].x, nd[j].y);
addEdge(nd[i].id, nd[j].id, dis*60/(1e4));
addEdge(nd[j].id, nd[i].id, dis*60/(1e4));
}
}
for(int i = 1; i <= line; i++){
for(int j = 0; j < vec[i].size() - 1; j++){
double dis = getDis(vec[i][j].x, vec[i][j].y, vec[i][j + 1].x, vec[i][j + 1].y);
addEdge(vec[i][j].id, vec[i][j + 1].id, dis*15/(1e4));
addEdge(vec[i][j + 1].id, vec[i][j].id, dis*15/(1e4));
}
}
spfa();
cout << (int)(d[2] + 0.5) << endl;
}
}
昂贵的聘礼 - POJ - 1062
思路
冬训的时候的题目,现在回去看自己当时的代码,实在是惨不忍睹……所以花了时间改了代码
将原物品的价格看作点的权值,而如果有替代品后的优惠价格看作边的权值,这样子从源点跑最短路,就能刚好符合题意。
跑的过程中注意维护等级限制的区间,最后,到每个点的最短路+该点的权值 中的最小值就是答案了
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>
using namespace std;
typedef long long ll;
const int N = 105;
const int inf = 0x3f3f3f3f;
struct node{
int u, val, upper, lower;
node(int pu, int pv, int pupper, int plower): u(pu), val(pv), upper(pupper), lower(plower) {}
bool operator < (const node& b) const { return val > b.val; }
};
struct edge { int v, val, next; };
int head[N];
edge e[N*N];
bool used[N];
int val[N];
int lv[N];
int d[N];
int tot;
priority_queue<node> que;
inline void init(){
memset(head, -1, sizeof(head));
memset(used, false, sizeof(used));
memset(d, 0x3f, sizeof(d));
d[1] = 0;
tot = 0;
}
void addEdge(int u, int v, int val){
e[tot].val = val;
e[tot].v = v;
e[tot].next = head[u];
head[u] = tot++;
}
void dijkstra(int u, int n, int lv_lim){
que.push(node(1, 0, lv[1] + lv_lim, lv[1] - lv_lim));
while(!que.empty()){
int u = que.top().u;
int val = que.top().val;
int upper = que.top().upper;
int lower = que.top().lower;
que.pop();
if(used[u] || val > d[u]) continue;
used[u] = true;
for(int i = head[u]; ~i; i = e[i].next){
int v = e[i].v;
if(!used[v] && lv[v] <= upper && lv[v] >= lower && d[v] > d[u] + e[i].val){
d[v] = d[u] + e[i].val;
int vupper = min(upper, lv[v] + lv_lim);
int vlower = max(lower, lv[v] - lv_lim);
que.push(node(v, d[v], vupper, vlower));
}
}
}
}
int main(){
int lv_lim, n;
while(~scanf("%d%d", &lv_lim, &n)){
init();
for(int u = 1; u <= n; u++){
int m;
scanf("%d%d%d", &val[u], &lv[u], &m);
while(m--){
int v, val;
scanf("%d%d", &v, &val);
addEdge(u, v, val);
}
}
dijkstra(1, n, lv_lim);
int ans = inf;
for(int u = 1; u <= n; u++){
ans = min(ans, val[u] + d[u]);
}
printf("%d\n", ans);
}
return 0;
}