重新给图论提高课做一个总结。
对于一个含有\(n\)个点,\(m\)条边的无向图,边权都是正值,求解起点到终点的最短距离。
根据\(n,m\)的数据范围选择邻接表或者邻接矩阵直接建图跑最短路就行,属于裸的板子题,难点在于如何抽象出图论模型来改板子吧。
多做多见多积累吧,灵光一现或许就是平时的日积月累的结果。
地址:https://www.acwing.com/problem/content/1131/
Code
#include <bits/stdc++.h> using namespace std; const int N = 2510, M = 2*6500+10; int h[N],e[M],ne[M],w[M],dis[N],q[N]; int n,m,S,T,idx; bool st[N]; void add(int a, int b, int c) { e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++; } void spfa() { memset(dis, 0x3f, sizeof dis); dis[S] = 0; st[S] = true; int hh = 0, tt = 1; q[0] = S; while( hh != tt ) { int t = q[hh++]; if (hh == N) hh = 0; st[t] = false; for (int i = h[t]; i != -1; i = ne[i]) { int j = e[i]; if (dis[j] > dis[t] + w[i]) { dis[j] = dis[t] + w[i]; if( !st[j] ) { q[tt++] = j; if (tt == N) tt = 0; st[j] = true; } } } } } int main(void) { cin >> n >> m >> S >> T; memset(h, -1, sizeof h); for (int i = 0; i < m; ++i ) { int a,b,c; cin >> a >> b >> c; add(a,b,c), add(b,a,c); } spfa(); cout << dis[T] << endl; return 0; }
地址:https://www.acwing.com/problem/content/1130/
求从起点出发,到所有点的最短距离的集合中的最大值,最远的都到了,其他点肯定早或同时收到信号。
本题求的是最长路,把代码的比较符号改改就行,数据量比较小。
Code
#include <bits/stdc++.h> using namespace std; int g[200][200]; int n, m; int main() { scanf("%d%d",&n,&m); memset(g,0x3f,sizeof g); for (int i = 1; i <= n; i++) g[i][i] = 0; for (int i = 1; i <= m; i++) { int a, b, c; scanf("%d%d%d",&a,&b,&c); g[a][b] = g[b][a] = min(g[a][b], c); } 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], g[i][k] + g[k][j]); } } } int res = -1; for (int i = 1; i <= n; i++) { if (g[1][i] == 0x3f3f3f3f) { res = -1; break; } else { res = max(res, g[1][i]); } } printf("%d\n",res); return 0; }
地址:https://www.acwing.com/problem/content/1129/
有\(n\)个点,\(m\)条边,边权都是正值。
求给定起点下,到达所有牧场的最短距离。
spfa可以解决,但是关于spfa它已经死了,所以保险起见我们最好跑堆优化的dijksra算法。
本题枚举所有点为起点,找一下该点到达所有点的最小值即可,数据范围较小。
Code
#include <bits/stdc++.h> using namespace std; #define debug freopen("in.in","r",stdin);freopen("out.out","w",stdout); #define IO ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr); #define rep(i, l, r) for(int i = (l); i <= (r); i++) #define per(i, r, l) for(int i = (r); i >= (l); i--) #define pb push_back #define SZ(x) ((int)(x).size()) #define fi first #define se second #define all(x) (x).begin(), (x).end() typedef long long ll; typedef double db; typedef pair<int, int> PII; const int inf = 123456789; int n, p, c; int dist[803], id[803]; bool vis[803]; vector<PII> e[1000]; int spfa(int start) { memset(dist,0x3f,sizeof dist); memset(vis,false,sizeof vis); queue<int> q; q.push(start); dist[start] = 0; vis[start] = 0; while (q.size()) { int now = q.front(); q.pop(); vis[now] = false; for (auto ver : e[now]) { if (dist[ver.fi] > dist[now] + ver.se) { dist[ver.fi] = dist[now] + ver.se; if (!vis[ver.fi]) { vis[ver.fi] = true; q.push(ver.fi); } } } } int sum = 0; for (int i = 1; i <= n; i++) { if (dist[id[i]] == 0x3f3f3f3f) return 0x3f3f3f3f; else sum += dist[id[i]]; } return sum; } int main(void) { scanf("%d%d%d",&n,&p,&c); for (int i = 1; i <= n; i++) { scanf("%d",&id[i]); } for (int i = 1; i <= c; i++) { int u, v, w; scanf("%d%d%d",&u,&v,&w); e[u].pb({v,w}); e[v].pb({u,w}); } int ans = inf; for (int i = 1; i <= p; i++) { ans = min(ans, spfa(i)); } printf("%d\n",ans); return 0; }
地址:https://www.acwing.com/problem/content/1128/
\(X * w_i*w_i+1...=100\),求X的最小值,即求后面一连串的最大值。
类比成求最长路,跑一个图论板子即可。
Code
#include <bits/stdc++.h> using namespace std; #define debug freopen("in.in","r",stdin);freopen("out.out","w",stdout); #define IO ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr); #define rep(i, l, r) for(int i = (l); i <= (r); i++) #define per(i, r, l) for(int i = (r); i >= (l); i--) #define pb push_back #define SZ(X) ((int)(x).size()) #define fi first #define se second #define all(x) (x).begin(), (x).end() typedef long long ll; typedef double db; typedef pair<int, int> PII; const int inf = 123456789; int n, m, s, e; double dist[503]; bool vis[503]; double g[503][503]; void dijskra() { dist[s] = 1; for (int i = 1; i <= n; i++) { int ver = -1; for (int j = 1; j <= n; j++) { if (!vis[j] && (ver == -1 || dist[ver] > dist[j])) { ver = j; } } vis[ver] = true; for (int j = 1; j <= n; j++) { dist[j] = max(dist[j], dist[ver] * g[ver][j]); } } } int main(void) { scanf("%d%d",&n,&m); for (int i = 1; i <= m; i++) { int a, b, c; scanf("%d%d%d",&a,&b,&c); double w = (100.0-c)/100; g[a][b] = g[b][a] = max(g[a][b],w); } scanf("%d%d",&s,&e); dijskra(); printf("%.8lf\n",100/dist[e]); return 0; }
地址:https://www.acwing.com/problem/content/922/
题意:
某个旅客想去公园游玩,但是他目前所在的地点没有一辆直达的巴士,他可能需要换乘几次后才能到达公园,现在给出所有巴士站编号和所有巴士路线,询问旅客到达公园的最少换乘次数。
思路:
换乘几次 = 乘车几次-1
可以看出,一条巴士路线上的所有巴士站,都可以沿着路线一直往后走,只算一次乘车,即答案+1。
那么我们可以建立一个权值都是1的图来进行求解,在这里如果我们直接依据题意建图,即一条线路上两两站点连线来求这个答案,显然是非常麻烦的,比如一个站点有多条线路时,你到下一站是属于换乘还是一条线路的判断,非常麻烦。
所以我们可以换一种灵活的建图方法,我们将一条线路上前面车站到后边可达车站建上一条边权为1的边即可,然后直接跑BFS即可。
#include <bits/stdc++.h> using namespace std; int n, m; int stop[1500], dist[503]; bool g[503][503]; void bfs() { memset(dist,0x3f,sizeof dist); dist[1] = 0; queue<int> q; q.push(1); while (q.size()) { int v = q.front(); q.pop(); for (int i = 1; i <= n; i++) { if (g[v][i] && dist[i] > dist[v] + 1) { dist[i] = dist[v] + 1; q.push(i); } } } } int main() { cin >> m >> n; string line; getline(cin,line); while (m--) { getline(cin,line); stringstream ssin(line); int cnt = 0, p; while (ssin >> p) { stop[cnt++] = p; } for (int j = 0; j < cnt; j++) { for (int k = j + 1; k < cnt; k++) { g[stop[j]][stop[k]] = true; } } } bfs(); if (dist[n] == 0x3f3f3f3f) { cout << "NO" << endl; } else { cout << dist[n]-1 << endl; } return 0; }
地址:https://www.acwing.com/problem/content/1139/
题意:
n个点m条边的有向图,不知道起点S,但知道终点E,求解从每个起点出发到达任意终点的所有路线的最短距离。
分析:
算法1
人为确定一个虚拟源点,并且虚拟源点练到所有起点一条边权为0的边,即原问题转化为从虚拟源点出发到达终点E的最短路。
算法2
反向建图,把终点置为起点,求终点开始到所有起点的单源最短路,再在之中取个最小值。
Code
感觉用SPFA的话,可以把所有起点压入队里跑。
#include <bits/stdc++.h> using namespace std; #define debug freopen("in.in","r",stdin);freopen("out.out","w",stdout); #define IO ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr); #define rep(i, l, r) for(int i = (l); i <= (r); i++) #define per(i, r, l) for(int i = (r); i >= (l); i--) #define pb push_back #define SZ(X) ((int)(x).size()) #define fi first #define se second #define all(x) (x).begin(), (x).end() typedef long long ll; typedef double db; typedef pair<int, int> PII; const int inf = 0x3f3f3f3f; const int N = 10010, M = 200010; int n, m, s, idx; int h[N], ne[M], e[M], w[M]; int dist[N]; bool vis[N]; void add(int a, int b, int c) { e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++; } void spfa() { memset(dist,0x3f,sizeof dist); queue<int> q; q.push(0); vis[0] = true; dist[0] = 0; while (q.size()) { int u = q.front(); q.pop(); vis[u] = false; for (int i = h[u]; ~i; i = ne[i]) { int j = e[i]; if (dist[j] > dist[u] + w[i]) { dist[j] = dist[u] + w[i]; if (!vis[j]) { vis[j] = true; q.push(j); } } } } } int main(void) { IO while (cin >> n >> m >> s) { memset(h,-1,sizeof h); idx = 0; rep(i,1,m) { int a, b, c; cin >> a >> b >> c; add(a,b,c); } int T; cin >> T; while (T--) { int ver; cin >> ver; add(0,ver,0); } spfa(); if (dist[s] == inf) { cout << "-1\n"; } else { cout << dist[s] << '\n'; } } return 0; }
地址:https://www.acwing.com/problem/content/1133/
题意:
巴拉巴拉巴拉太长了自己看。
分析:
简易版本:一个迷宫,从起点S走到终点E的最短路长度,每个格子可上下左右四个方向扩展,格子的属性有两种,连通和不连通(有障碍物),需要记录的状态就是当前坐标以及到达当前坐标的长度,可扩展坐标和可扩展坐标最短路长度。
到本题这里,格子的属性更复杂了,可以直达的格子里分为有钥匙和没钥匙两种,拿去钥匙不计时间,有障碍物的格子分为不可达的障碍物,或者需要某一类钥匙才可以通过的门。
那么我们需要记录的状态也就更多了,除了上述状态,还需要记录此时持有钥匙的状态,即我们需要维护一个三维状态\(f(x,y,state)\),来表明在\((x,y)\)这个坐标时的持有钥匙状态。
接下来我们考虑走到下一个格子后,如何进行状态处理。本题因为可以走回头路,不存在拓扑序,存在环形路径的可能,所以用DP无法计算状态转移,需要使用最短路算法。
我们定义当前点为\((x,y)\),状态为\(state\)。
本题空间为64MB,直接开三维数组存状态会MLE,所以我们将状态压缩一下,将\((x,y)\)压缩成一个独立的点来表示,每类钥匙可以用二进制来表示,拿起钥匙就是|一下,将对应位置1即可,判断有无钥匙则将状态右移对应位看是不是1就可以。
建图细节看代码,这里不再赘述。
Code
#include <bits/stdc++.h> using namespace std; #define debug freopen("in.in","r",stdin);freopen("out.out","w",stdout); #define IO ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr); #define rep(i, l, r) for(int i = (l); i <= (r); i++) #define per(i, r, l) for(int i = (r); i >= (l); i--) #define pb push_back #define SZ(X) ((int)(x).size()) #define fi first #define se second #define all(x) (x).begin(), (x).end() typedef long long ll; typedef double db; typedef pair<int, int> PII; const int inf = 123456789; const int N = 11, M = 400, P = 1 << 10; int n, m, k, p; int h[N * N], e[M], w[M], ne[M], idx; int g[N][N], key[N * N]; int dist[N*N][P]; bool vis[N * N][P]; set<PII> Edge; void add(int a, int b, int c) { e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++; } void build() { int dx[] = {-1,0,1,0}, dy[] = {0,1,0,-1}; for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { for (int u = 0; u < 4; u++) { int x = i + dx[u], y = j + dy[u]; if (!x || x > n || !y || y > m) continue; int a = g[i][j], b = g[x][y]; if (!Edge.count({a,b})) { add(a,b,0); } } } } } int bfs() { memset(dist,0x3f,sizeof dist); dist[1][0] = 0; deque<PII> q; q.push_back({1,0}); while (q.size()) { PII u = q.front(); q.pop_front(); if (vis[u.fi][u.se]) continue; vis[u.fi][u.se] = true; if (u.fi == n * m) { return dist[u.fi][u.se]; } if (key[u.fi]) { int state = u.se | key[u.fi]; if (dist[u.fi][state] > dist[u.fi][u.se]) { dist[u.fi][state] = dist[u.fi][u.se]; q.push_front({u.fi, state}); } } for (int i = h[u.fi]; i != -1; i = ne[i]) { int j = e[i]; if (w[i] && !(u.se >> w[i]-1 & 1)) continue; if (dist[j][u.se] > dist[u.fi][u.se] + 1) { dist[j][u.se] = dist[u.fi][u.se] + 1; q.push_back({j,u.se}); } } } return -1; } int main(void) { IO cin >> n >> m >> p >> k; for (int i = 1, t = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { g[i][j] = t++; } } memset(h,-1,sizeof h); while (k--) { int x1, y1, x2, y2, c; cin >> x1 >> y1 >> x2 >> y2 >> c; int a = g[x1][y1], b = g[x2][y2]; Edge.insert({a,b}), Edge.insert({b,a}); if (c) { add(a,b,c), add(b,a,c); } } build(); int s; cin >> s; while (s--) { int x, y, c; cin >> x >> y >> c; key[g[x][y]] |= 1 << c-1; } cout << bfs() << '\n'; return 0; }
地址:https://www.acwing.com/problem/content/1136/
题意:
给出一个 N 个顶点 M 条边的无向无权图,顶点编号为 1到 N。
问从顶点 1 开始,到其他每个点的最短路有几条。
分析:
先前条件:最短路中不存在值为0的环,否则可以一直在环里跑,无解。
先前概念:最短路径树指的是以某个节点为根,根节点到其他点距离最小形成的一棵树。
从每个点的前一个路径考虑,看看该点时从哪个路径转移过来的,累加上来就好,但是我们需要先弄出来一个最短路径树(拓扑图)。对于转移过来的边,我们根据长度为两种。
对于最短路径树,我们可以通过BFS算法或者dijkstra算法实现。
Code
#include <bits/stdc++.h> using namespace std; #define debug freopen("in.in","r",stdin);freopen("out.out","w",stdout); #define IO ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr); #define rep(i, l, r) for(int i = (l); i <= (r); i++) #define per(i, r, l) for(int i = (r); i >= (l); i--) #define pb push_back #define SZ(X) ((int)(x).size()) #define fi first #define se second #define all(x) (x).begin(), (x).end() typedef long long ll; typedef double db; typedef pair<int, int> PII; const int inf = 123456789; const int N = 100010, M = 400010; const int mod = 100003; int n, m; int h[N], e[M], ne[M], q[M]; int dist[N], cnt[N]; int idx; void add(int a, int b) { e[idx] = b, ne[idx] = h[a], h[a] = idx++; } void bfs() { memset(dist,0x3f,sizeof dist); dist[1] = 0, cnt[1] = 1; int front = 0, rear = 0; q[0] = 1; while (front <= rear) { int t = q[front++]; for (int i = h[t]; ~i; i = ne[i]) { int j = e[i]; if (dist[j] > dist[t] + 1) { dist[j] = dist[t] + 1; cnt[j] = cnt[t]; q[++rear] = j; } else if (dist[j] == dist[t] + 1) { cnt[j] = (cnt[j] + cnt[t]) % mod; } } } } int main(void) { IO cin >> n >> m; memset(h,-1,sizeof h); while (m--) { int a, b; cin >> a >> b; add(a,b),add(b,a); } bfs(); for (int i = 1; i <= n; i++) { cout << cnt[i] << "\n"; } return 0; }
地址:https://www.acwing.com/problem/content/385/
题意:
求次短路长度数目。
分析:
和上一题差不多,只不过状态转移麻烦很多。
Code
#include <bits/stdc++.h> using namespace std; #define debug freopen("in.in","r",stdin);freopen("out.out","w",stdout); #define IO ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr); #define rep(i, l, r) for(int i = (l); i <= (r); i++) #define per(i, r, l) for(int i = (r); i >= (l); i--) #define pb push_back #define SZ(X) ((int)(x).size()) #define fi first #define se second #define all(x) (x).begin(), (x).end() typedef long long ll; typedef double db; typedef pair<int, int> PII; const int inf = 123456789; const int N = 1010, M = 100010; int n, m, S, E, idx; int h[N],ne[M],e[M],w[M]; int dist[N][2], cnt[N][2]; bool vis[N][2]; struct Node { int id, type, dist; bool operator > (const Node& tmp) const { return dist > tmp.dist; } }; void add(int a, int b, int c) { e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++; } int dij() { memset(vis,false,sizeof vis); memset(dist, 0x3f, sizeof dist); memset(cnt, 0, sizeof cnt); dist[S][0] = 0, cnt[S][0] = 1; priority_queue<Node, vector<Node>, greater<Node>> heap; heap.push({S,0,0}); while (heap.size()) { Node u = heap.top(); heap.pop(); int ver = u.id, type = u.type, distance = u.dist, count = cnt[ver][type]; if (vis[ver][type]) continue; vis[ver][type] = true; for (int i = h[ver]; ~i; i = ne[i]) { int j = e[i]; if (dist[j][0] > distance + w[i]) { dist[j][1] = dist[j][0]; cnt[j][1] = cnt[j][0]; heap.push({j,1,dist[j][1]}); dist[j][0] = distance + w[i]; cnt[j][0] = count; heap.push({j,0,dist[j][0]}); } else if (dist[j][0] == distance + w[i]) { cnt[j][0] += count; } else if (dist[j][1] > distance + w[i]) { dist[j][1] = distance + w[i]; cnt[j][1] = count; heap.push({j,1,dist[j][1]}); } else if (dist[j][1] == distance + w[i]) { cnt[j][1] += count; } } } int res = cnt[E][0]; if (dist[E][1] - 1 == dist[E][0]) { res += cnt[E][1]; } return res; } int main(void) { IO int T; cin >> T; while (T--) { cin >> n >> m; memset(h,-1,sizeof h); idx = 0; while (m--) { int a,b,c; cin >> a >> b >> c; add(a,b,c); } cin >> S >> E; cout << dij() << '\n'; } return 0; }