目录
3)Heavy Transportation POJ - 1797 :“最大瓶颈路”,求最小值最大的路径。(最小瓶颈路,是求最大值最小的路径)。
5)Currency Exchange POJ - 1860 :spfa判正环
6)Wormholes POJ - 3259 :spfa判负环
9)Arbitrage POJ - 2240 :Floyd判正环
15)Extended Traffic LightOJ - 1074 :
待处理(spfa+dfs标记负环),参考博客:Light OJ 1074 Extended Traffic 【spfa+dfs标记负环及相连点】
17)Marriage Match IV HDU - 3416 :
19)Layout POJ - 3169 :差分约束+spfa判负环
最短路知识复习&总结(全)
本专题20210415-20210427,12天,差一点两周,东西很多,收获很多。
写在前面
1.这些题都很好,很多标记没有价值只是因为重复(或者说并没有新的知识,里面有的题题意很值得思考,有的建图需要注意,反正整套题都挺好的。ps:最短路算法,虽然它基础,但是它实在太有用了,太多可拓展的了。)
2.有不少题直接觉得没有新知识就没做了emm。搞了近两周,确实累了[捂脸],至于总结知识点,倒是所有遇到的都总结了。
所有学过的最短路相关的这是,都在这两个博客里面了。有点累,等啥时候缓过来了主要知识点全部总结到最短路知识复习&总结(全),现在最短路相关的东西也只看这两个博客就可以了。
3.似乎更感兴趣与学习新东西,这两周全在弄最短路,确实乏了。冲一波网络流,然后数论,字符串等等等等。找点灵(新鲜)感。
题目
1)Til the Cows Come Home POJ - 2387 :模板题,脑瘫题(题都没说清楚)(无价值)。
1.邻接表要判重,vector不用判重。
2)Frogger POJ - 2253 :最小瓶颈路
题意:n(1~200)个位置,第i个位置的坐标(xi,yi)(0~1000整数)。青蛙最开始在1,要跳到2。问青蛙最少能跳远才能到达2。
题解:最短路求最小瓶颈路。——理论上floyd,dijkstra,spfa都可以实现。最小生成树也可以实现,最小生成树满足任意两点的在最小生成树上的简单路径都是这两点在原图中的最小瓶颈路。
代码:
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <map>
#include <queue>
#include <set>
#include <stack>
#include <string>
#include <vector>
using namespace std;
const int maxn = 2e2 + 10;
const double eps = 1e-8;
const double inf = 1e9;
int n;
double x[maxn], y[maxn];
double g[maxn][maxn];
void init() {
for (int i = 0; i <= n; i++)
for (int j = 0; j <= n; j++) g[i][j] = inf;
}
double dis(int i, int j) {
return sqrt((x[j] - x[i]) * (x[j] - x[i]) + (y[j] - y[i]) * (y[j] - y[i]));
}
signed main() {
int cas = 0;
while (scanf("%d", &n) != EOF) {
if (n == 0) break;
init();
for (int i = 1; i <= n; i++) scanf("%lf%lf", &x[i], &y[i]);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++) g[i][j] = dis(i, j);
for (int k = 1; k <= n; k++)
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
g[i][j] = min(g[i][j], max(g[i][k], g[k][j])); //最小瓶颈路
printf("Scenario #%d\nFrog Distance = %.3lf\n\n", ++cas, g[1][2]);
}
return 0;
}
3)Heavy Transportation POJ - 1797 :“最大瓶颈路”,求最小值最大的路径。(最小瓶颈路,是求最大值最小的路径)。
题意:求1到n的路径中的最小权值的最大值。n(1~1000)个点,m条无向带权边(权值1~1000000)。(题目原意:权值为路能承受的最大重量,求货车能装的最大重量,再大一点就会把路压垮)。
题解:理论上floyd,dijkstra,spfa都能实现。另外也可以像求最小瓶颈路一样,先求出“最大生成树”,然后查询树中的简单路径中的最小值。
1)这里floyd超时了。
2)比较熟悉spfa,注意松弛操作的时候没入队的点才能入队。
代码:
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <map>
#include <queue>
#include <set>
#include <stack>
#include <string>
#include <vector>
#define pb push_back
using namespace std;
const int maxn = 1e3 + 10;
const int inf = 1e9;
struct node {
int v, w;
node() {}
node(int _v, int _w) { v = _v, w = _w; }
};
int n, m, u, v, w;
vector<node> g[maxn];
void init() {
for (int i = 0; i <= n; i++) g[i].clear();
}
int dis[maxn], vis[maxn];
void spfa() {
for (int i = 0; i <= n; i++) dis[i] = 0, vis[i] = 0;
vis[1] = 1, dis[1] = inf;
queue<int> q;
q.push(1);
while (!q.empty()) {
int x = q.front();
q.pop();
vis[x] = 0;
for (int j = 0; j < g[x].size(); j++) {
node i = g[x][j];
if (min(dis[x], i.w) > dis[i.v]) {
dis[i.v] = min(dis[x], i.w);
//注意spfa没在队列中才加入!!!
if (!vis[i.v]) vis[i.v] = 1, q.push(i.v);
}
}
}
}
signed main() {
int cas = 0, T;
scanf("%d", &T);
while (T--) {
scanf("%d%d", &n, &m);
init();
for (int i = 1; i <= m; i++) {
scanf("%d%d%d", &u, &v, &w);
g[u].pb(node(v, w)), g[v].pb(node(u, w));
}
spfa();
printf("Scenario #%d:\n%d\n\n", ++cas, dis[n]);
}
return 0;
}
4)Silver Cow Party POJ - 3268 :(模板题,没啥价值)
题意(写一下,万一有人不想读题呢):n(1~1000)个点,终点为x,m(1~100000)条带权有向边(Ti表示走第i条路需要花费的时间,大小在1~100)。求所有点到x在返回原来位置的最少时间的最大值。
5)Currency Exchange POJ - 1860 :spfa判正环
题意:货币的数量为N(1~100),货币兑换点的数量为N(1~100),最开始持有的货币为S,数量为V(0~1e3为实数)。输入M行:两货币的种类a,b、a->b的汇率和手续费,b->a的汇率和手续费。判断是否能通过一系列的操作使所持货币总量增加。
题解:建图算是一个难点(其实也没多难)。spfa最长路判正环,spfa最短路判负环。
代码:
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <map>
#include <queue>
#include <set>
#include <stack>
#include <string>
#include <vector>
#define pb push_back
#define read(x) scanf("%d", &x)
using namespace std;
const int maxn = 1e2 + 10;
const double inf = 1e9;
const double eps = 1e-8;
int sgn(double x) {
if (fabs(x) < eps) return 0;
if (x < 0)
return -1;
else
return 1;
}
struct node {
int to;
double ex, co;
node() {}
node(int _to, double _ex, double _co) { to = _to, ex = _ex, co = _co; }
};
int n, m, s, a, b;
double v, ex, co;
vector<node> g[maxn];
int vis[maxn];
double dis[maxn];
bool spfa() {
for (int i = 1; i <= n; i++) dis[i] = -inf, vis[i] = 0;
queue<int> q;
q.push(s), vis[s] = 1, dis[s] = v;
while (!q.empty()) {
int x = q.front();
q.pop();
vis[x] = 0;
if (sgn(dis[s] - v) > 0) return true;
for (int j = 0; j < g[x].size(); j++) {
node i = g[x][j];
//这里的松弛操作,只需要记住一点--能更新就更新。
if (sgn(dis[i.to] - (dis[x] - i.co) * i.ex) < 0) {
dis[i.to] = (dis[x] - i.co) * i.ex;
if (!vis[i.to]) q.push(i.to), vis[i.to] = 1;
}
}
}
return false;
}
signed main() {
read(n), read(m), read(s);
scanf("%lf", &v);
for (int i = 1; i <= m; i++) {
read(a), read(b);
scanf("%lf%lf", &ex, &co);
g[a].pb(node(b, ex, co));
scanf("%lf%lf", &ex, &co);
g[b].pb(node(a, ex, co));
}
puts(spfa() ? "YES" : "NO");
return 0;
}
6)Wormholes POJ - 3259 :spfa判负环
题意:n(1~500)个农场,m(1~2500)条无向带权路径(s,e,t。路径花费t大于0且不超过10000s),w(1~200)个虫洞(单向路径,s,e,t。t不大于10000s,表示可以向前回溯t秒)。问是否可以回到出发位置,并且见到出发前的自己。"YES",“NO”。
注意:没说起点,不过无向图,任意两点都能到达,有一个负环则从任意一点出发都能回到这点的出发之前;s-e间的虫洞可能不止一条(不过vector就不必特殊考虑了)。
题解:spfa判负环。
代码:
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <map>
#include <queue>
#include <set>
#include <stack>
#include <string>
#include <vector>
#define pb push_back
#define read(x) scanf("%d", &x)
using namespace std;
const int maxn = 5e2 + 10;
const int inf = 1e9;
struct node {
int to, w;
node() {}
node(int _to, int _w) { to = _to, w = _w; }
};
int n, m, w, a, b, c;
vector<node> g[maxn];
int vis[maxn], dis[maxn], cnt[maxn];
bool spfa() {
for (int i = 0; i <= n; i++) vis[i] = cnt[i] = 0, dis[i] = inf;
queue<int> q;
q.push(1), vis[1] = 1, dis[1] = 0, cnt[1] = 1;
while (!q.empty()) {
int x = q.front();
q.pop();
vis[x] = 0;
if (cnt[x] > n) return true;
for (int j = 0; j < g[x].size(); j++) {
node i = g[x][j];
if (dis[x] + i.w < dis[i.to]) {
dis[i.to] = dis[x] + i.w;
cnt[i.to] = cnt[x] + 1; //为什么这么操作?
if (!vis[i.to]) vis[i.to] = 1, q.push(i.to);
}
}
}
return false;
}
signed main() {
int T;
read(T);
while (T--) {
read(n), read(m), read(w);
//初始化
for (int i = 1; i <= n; i++) g[i].clear();
for (int i = 1; i <= m; i++) {
read(a), read(b), read(c);
g[a].pb(node(b, c)), g[b].pb(node(a, c));
}
for (int i = 1; i <= w; i++) {
read(a), read(b), read(c);
g[a].pb(node(b, -c));
}
puts(spfa() ? "YES" : "NO");
}
return 0;
}
7)MPI Maelstrom POJ - 1502 :模板题(无价值的)。
8)Cow Contest POJ - 3660 :传递闭包。与之前总结的几乎一毛一样的题目:最短路知识复习&总结(全)
9)Arbitrage POJ - 2240 :Floyd判正环
题意:n(1~30)种货币(每种货币用字符串表示),然后m种交换方式:货币ci val cj表示货币ci交换cj汇率为val(即ci=val*cj。val为小数)。问是否可以套现(经过一系列操作之后钱变多了)。
题解:Floyd判正环(求最长路,然后最后检查g[i][i]是否大于1)。最短路的话应该就可以判负环了emm。
代码:
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <map>
#include <queue>
#include <set>
#include <stack>
#include <string>
#include <vector>
#define read(x) scanf("%d", &x)
using namespace std;
const int maxn = 30 + 10;
const double eps = 1e-8;
int n, m;
double w, g[maxn][maxn];
string str, s1, s2;
map<string, int> mp;
bool floyd() {
for (int k = 1; k <= n; k++)
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
g[i][j] = max(g[i][j], g[i][k] * g[k][j]);
for (int i = 1; i <= n; i++)
if (g[i][i] > 1) return true;
return false;
}
signed main() {
int cas = 0;
while (scanf("%d", &n) != EOF) {
if (n == 0) break;
mp.clear();
memset(g, 0, sizeof(g));
for (int i = 1; i <= n; i++) cin >> str, mp[str] = i;
read(m);
for (int i = 1; i <= m; i++) {
cin >> s1 >> w >> s2;
g[mp[s1]][mp[s2]] = w;
}
printf("Case %d: %s\n", ++cas, floyd() ? "Yes" : "No");
}
return 0;
}
10)Invitation Cards POJ - 1511 :模板题(无价值)
11)Candies POJ - 3159:差分约束
题意:n(<=30000)个同学,m(<=150000)个关系(a,b,c),表示b-a的数量不能超过c。问n最多比1多多少颗糖果,保证一定存在答案(一定有解)。
差分约束(见下图)
题解:每个b-a<=c转化为a->b权值为c即可,然后从起点到终点构成一个不等式链。求最大值则求最短路,最小值则最长路。因为一定有解,所以不存在负环,而且一定能到达(答案不会无穷大——比如任何约束时就无穷大)。
代码(超时了,改成前向星即可):
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <map>
#include <queue>
#include <set>
#include <stack>
#include <string>
#include <vector>
// #define int long long
#define read(x) scanf("%d", &x)
#define print(a, c) printf("%d%c", a, c)
#define pb push_back
#define mst(a, i) memset(a, i, sizeof(a))
#define dbg(x) cout << #x << "===" << x << endl
using namespace std;
const double eps = 1e-8;
const int maxn = 3e4 + 10;
const int mod = 1e9 + 7;
const int inf = 1e9 + 7;
struct node {
int to, w;
node(int _to = 0, int _w = 0) : to(_to), w(_w) {}
};
struct pii {
int fi, se;
pii(int fi, int se) : fi(fi), se(se) {}
bool operator<(pii b) const { return fi > b.fi; }
};
int n, m, a, b, c;
vector<node> g[maxn];
int dis[maxn], vis[maxn];
void dijkstra(int s) {
for (int i = 0; i <= n; i++) dis[i] = inf;
priority_queue<pii> q;
q.push(pii(0, s)), dis[s] = 0;
while (!q.empty()) {
int x = q.top().se;
q.pop();
if (vis[x]) continue;
vis[x] = 1;
int j, len = g[x].size();
for (j = 0; j < len; j++) {
node i = g[x][j];
if (dis[x] + i.w < dis[i.to]) {
dis[i.to] = dis[x] + i.w;
q.push(pii(dis[i.to], i.to));
}
}
}
}
signed main() {
read(n), read(m);
for (int i = 1; i <= m; i++) {
read(a), read(b), read(c);
g[a].pb(node(b, c));
}
dijkstra(1);
// for (int i = 1; i <= n; i++) cout << i << ":::" << dis[i] << endl;
printf("%d", dis[n]);
return 0;
}
12)Subway POJ - 2502 :最短路模板题,建图有一点难。(可以说无价值)
13)昂贵的聘礼 POJ - 1062:最短路模板题,枚举最小值!!
ps(防漏):1.超级源点这个东西,也很强!(之前只是没把他当作一个知识点重视)。
2.四舍五入:int(a+0.5);四舍五入到整数的话只看小数第一位,<4则舍,>5则入。
14)Tram POJ - 1847:最短路模板题,别害怕读题!!可以说只是题目有点难懂(当然题意这个东西也不能吊椅倾心,但没多大必要死磕)。
15)Extended Traffic LightOJ - 1074 :
待处理(spfa+dfs标记负环),参考博客:Light OJ 1074 Extended Traffic 【spfa+dfs标记负环及相连点】
16)The Shortest Path in Nya Graph HDU - 4725 :dijkstra+堆+分层建图。分层图,见博客最短路知识复习&总结(全)中的(六)即可。
17)Marriage Match IV HDU - 3416 :
待处理,涉及到网络流
18)0 or 1 HDU - 4370 :
待处理,涉及到最小环
19)Layout POJ - 3169 :差分约束+spfa判负环
题意:给定n(<=1000)个点,n个点按顺序排在一条线上(从1到n,可能在同一个位置!),m1(<=10000)个关系表示a,b差距不能大于c(b在a前面),m2(<=10000)个关系表示a,b差距不能小于c(b在a前面)。
题解:才学差分约束(具体见题11)——poj3159),感觉理解不是很透彻。总之抓住(求最大值的情况,求最小值差不多):
1)找到所有的不等式条件,转化为b-a<=c,然后就可以建有向边a->b,权值为c。
注意这一题的隐形条件,第i+1个数的位置大于等于第i个数的位置。
2)然后建好超级源点,从超级源点出发,如果存在负环则表示无解(必须从超级源点出发,因为要兼顾所有的点——如果要求满足题目中的所有条件)。
3)满足条件之后再求需要的两点之间的关系。如果两点距离为inf则表示两点之差可以为无穷大。
代码:
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <map>
#include <queue>
#include <set>
#include <stack>
#include <string>
#include <vector>
// #define int long long
#define read(x) scanf("%d", &x)
#define print(a, c) printf("%d%c", a, c)
#define pb push_back
#define mst(a, i) memset(a, i, sizeof(a))
#define dbg(x) cout << #x << "===" << x << endl
using namespace std;
const double eps = 1e-8;
const int maxn = 1e3 + 10;
const int mod = 1e9 + 7;
const int inf = 1e9 + 7;
struct node {
int to, w;
node(int _to = 0, int _w = 0) : to(_to), w(_w) {}
};
int n, m1, m2, a, b, c;
vector<node> g[maxn];
int dis[maxn], vis[maxn], cnt[maxn];
bool spfa(int s) {
for (int i = 0; i <= n; i++) dis[i] = inf, vis[i] = cnt[i] = 0;
queue<int> q;
q.push(s), vis[s] = 1, dis[s] = 0;
while (!q.empty()) {
int x = q.front();
q.pop();
vis[x] = 0;
if (cnt[x] > n) return true; //负环
int j, len = g[x].size();
for (j = 0; j < len; j++) {
node i = g[x][j];
if (dis[x] + i.w < dis[i.to]) {
dis[i.to] = dis[x] + i.w;
cnt[i.to] = cnt[x] + 1;
if (!vis[i.to]) q.push(i.to), vis[i.to] = 1;
}
}
}
return false;
}
signed main() {
read(n), read(m1), read(m2);
for (int i = 1; i <= m1; i++) {
// b-a<=c
read(a), read(b), read(c);
g[a].pb(node(b, c));
}
for (int i = 1; i <= m2; i++) {
read(a), read(b), read(c);
//注意是a-b<=-c
g[b].pb(node(a, -c));
}
// 0为超级源点
for (int i = 1; i <= n; i++) g[0].pb(node(i, 0));
//注意条件,按照顺序,那么i+1个点的距离大于等于第i个点的距离
for (int i = 1; i < n; i++) g[i + 1].pb(node(i, 0));
// spfa(0)只是为了找是否存在负环
if (spfa(0))
puts("-1"); //存在负环
else {
// spfa(1)才是为了找最大值
spfa(1);
if (dis[n] == inf)
puts("-2"); //约束不足,答案可以为无穷大
else
print(dis[n], '\n');
}
return 0;
}
/*
2 2
1 2 5
2 1 4
*/