存图
存边
直接开一个结构体数组存边
struct Edge { int begin, end, weight; } edge[10010]; int edge_count; inline void AddEdge(const int &u, const int &v, const int &w) { edge[edge_count++] = Edge {u, v, w}; }
应用:
- Kruskal's algorithm
Adjacency matrix
用二维数组adj[i][j]
表示\(i\)与\(j\)的关系
int adj[1010][1010]; #define ADD_EDGE(u, v, w) adj[u][v] = w
应用:
- Floyd-Warshall algorithm
- Hangarian algorithm
- Kuhn-Munkres algorithm
Adjacency list
有几种形式, 以adj[i]
表示以\(i\)为开头的边
应用: 各种图论算法
vector
优点: 访问方便, 存图方便
缺点: 消耗空间, 容易\(MLE\); 删边速度慢
struct Edge { int destination, weight; }; std::vector<Edge> adj[1010];
加边
#define ADD_EDGE(u, v, w) adj[u].push_back(Edge {v, w})
访问
for (register int i(0); i < adj[u].size(); ++i) { adj[u][i]... ... }
或
for (auto i : adj[u]) { i... ... }
list
优点: 添边删边速度快
缺点: 不易访问; 容易\(MLE\)
struct Edge { int destination, weight; }; std::list<Edge> adj[1010];
加边
#define ADD_EDGE(u, v, w) adj[u].push_back(Edge {v, w})
访问
for (register std::list<Edge>::iterator i = adj[u].begin(); i != adj[u].end(); ++i) { *i... ... }
或
for (auto i : adj[u]) { i... ... }
链式前向星
优点: 空间重复利用, 通常不会\(MLE\), 而且很快
缺点: 开小了会\(WA\), 开大了会\(TLE\), 有时还会\(RE\)
应用: 各种图论算法
struct Edge { int destination, weight, next; } edge[10010]; int head[1010], edge_count;
加边
inline void AddEdge(const int &u, const int &v, const int &w) { edge[edge_count] = Edge {v, w, head[u]}, head[u] = edge_count++; }
访问
for (register int i(head[u]); i != -1; i = edge[i].next) { edge[i]... ... }
最小生成树
题目链接: Luogu P3366 【模板】最小生成树
Kruskal's algorithm
将边以权值从小到大排序遍历, 用并查集加边, 同时维护答案。
特点: 适合稀疏图
时间复杂度: \(\Theta(|E| \lg |V|)\)
#include <cstdio> #include <algorithm> struct Edge { int begin, end, weight; inline bool operator <(const Edge &another) const { return this->weight < another.weight; } } edge[200010]; int unions[5010]; int n, m; inline int Find(const int&); inline int Kruskal(); int main(int argc, char const *argv[]) { scanf("%d %d", &n, &m); for (register int i(0); i <= n; ++i) { unions[i] = i; } for (register int i(0), u, v, w; i < m; ++i) { scanf("%d %d %d", &u, &v, &w), edge[i] = Edge {u, v, w}; } register int ans(Kruskal()); if (ans) printf("%d\n", ans); else puts("orz"); return 0; } inline int Find(const int &x) { return unions[x] == x ? x : (unions[x] = Find(unions[x])); } inline int Kruskal() { register int ret(0); std::sort(edge, edge + m); for (register int i(0), countt(0), u, v; i < m; ++i) { u = Find(edge[i].begin), v = Find(edge[i].end); if (u != v) { unions[v] = u, ++countt, ret += edge[i].weight; if (countt == n - 1) { return ret; } } } return 0; }
Prim's algorithm
随机选择一个结点作为树, 每次找与这棵树相连且不构成环的最小边加入进来, 形成生成树。
可以使用优先队列优化。
特点: 适合稠密图
时间复杂度: \(\Theta(|E| \lg |V|)\)
#include <cstdio> #include <queue> #include <vector> #include <cstring> struct Edge { int destination, weight; }; std::vector<Edge> adj[5010]; int distance[5010], vis[5010]; int n, m; inline int Prim(); int main(int argc, char const *argv[]) { memset(distance, 0x3f, sizeof(distance)); scanf("%d %d", &n, &m); for (register int i(0), u, v, w; i < m; ++i) { scanf("%d %d %d", &u, &v, &w); adj[u].push_back(Edge {v, w}), adj[v].push_back(Edge {u, w}); } register int ans(Prim()); if (ans) printf("%d\n", ans); else puts("orz"); return 0; } inline int Prim() { register int ret(0), countt(0); distance[1] = 0; std::priority_queue<std::pair<int, int>, std::vector<std::pair<int, int> >, std::greater<std::pair<int, int> > > Q; Q.push(std::pair<int, int>(0, 1)); while (!Q.empty()) { register int w(Q.top().first), u(Q.top().second); Q.pop(); if (!vis[u]) { ++countt, ret += w, vis[u] = 1; for (auto i : adj[u]) { if (i.weight < distance[i.destination]) { distance[i.destination] = i.weight, Q.push(std::pair<int, int>(distance[i.destination], i.destination)); } } } } return ret * (countt == n); }
强连通分量
题目链接: USACO 06 Jan. The Cow Prom
Tarjan's strongly connected components algorithm
是基于对图深度优先搜索(DFS)的算法, 每个强连通分量为搜索树中的一棵子树。搜索时, 把当前搜索树中未处理的节点加入一个堆栈, 回溯时可以判断栈顶到栈中的节点是否为一个强连通分量。
时间复杂度: \(\Theta(|V|+|E|)\)
#include <cstdio> #include <vector> #include <stack> #include <algorithm> std::vector<int> adj[10010]; int dfn[10010], low[10010], color_count[10010], indexx, countt; bool instack[10010]; int n, m, ans; std::stack<int> S; inline int Tarjan(const int&); int main(int argc, char const *argv[]) { scanf("%d %d", &n, &m); for (register int i(0), u, v;i < m; ++i) { scanf("%d %d", &u, &v); adj[u].push_back(v); } for (register int i(1); i <= n; ++i) { if (!dfn[i]) { Tarjan(i); } } for (register int i(1); i <= countt; ++i) { if (color_count[i] > 1) ++ans; } printf("%d\n", ans); return 0; } inline void Tarjan(const int &cur) { dfn[cur] = low[cur] = ++indexx; S.push(cur), instack[cur] = 1; for (auto i : adj[cur]) { if (!dfn[i]) { Tarjan(i); low[cur] = std::min(low[cur], low[i]); } else if (instack[i]) { low[cur] = std::min(low[cur], low[i]); } } if (dfn[cur] == low[cur]) { ++countt; while (S.top() != cur) { register int node(S.top()); S.pop(), instack[node] = 0, ++color_count[countt]; } S.pop(), ++color_count[countt], instack[cur] = 0; } }
二分图最大匹配
题目链接: Luogu P3386 【模板】二分图匹配
Hungarian algorithm
通过寻找增广路来计算最大匹配值
时间复杂度:
- Adjacency matrix: \(\Theta(n^3)\)
- Adjacency list: \(\Theta(nm)\)
#include <cstdio> #include <vector> #include <cstring> std::vector<int> adj[1010]; int n, m, e; int vis[1010], mate[1010]; inline bool Match(const int&); inline int Hungarian(); int main(int argc, char const *argv[]) { scanf("%d %d %d", &n, &m ,&e); for (register int i(0), u, v; i < e; ++i) { scanf("%d %d", &u, &v); if (u <= n && v <= m) { adj[u].push_back(v); } } printf("%d\n", Hungarian()); return 0; } inline int Hungarian() { register int ret(0); for (register int i(1); i <= n; ++i) { memset(vis, 0, sizeof(vis)); ret += Match(i); } return ret; } inline bool Match(const int &cur) { for (auto i : adj[cur]) { if (!vis[i]) { vis[i] = 1; if (!mate[i] || Match(mate[i])) { mate[i] = cur; return true; } } } return false; }
二分图最佳匹配
Kuhn-Munkres algorithm
是一种逐次修改可行顶标的方法,使之对应的等价子图逐次增广(增加边),最后出现完备匹配.
时间复杂度: \(\Theta(n^3)\)
#include <cstdio> #include <cstring> int nl, nr; int adj[310][310]; int mate[310], dist_l[310], dist_r[310]; int slack[310]; bool vis_l[310], vis_r[310]; int n; inline bool BestMatch(const int&); inline int KuhnMunkres(); int main(int argc, char const *argv[]) { while(~scanf("%d", &n)) { for (register int i(0); i < n; ++i) { for(register int j(0); j < n; ++j) { scanf("%d", &adj[i][j]); } } nl = nr = n; printf("%d\n" ,KuhnMunkres()); } return 0; } inline bool BestMatch(const int &cur) { vis_l[cur] = true; for (register int r(0); r < nr; ++r) { if (!vis_r[r]) { register int tmp(dist_l[cur] + dist_r[r] - adj[cur][r]); if (!tmp) { vis_r[r] = true; if(!~mate[r] || BestMatch(mate[r])){ mate[r] = cur; return true; } } else if(slack[r] > tmp) slack[r] = tmp; } } return false; } inline int KuhnMunkres() { memset(mate, -1, sizeof(mate)); memset(dist_r, 0, sizeof(dist_r)); for (register int i(0); i < nl; ++i) { dist_l[i] = 0x80000000; for (register int j(0); j < nr; ++j) { if (adj[i][j] > dist_l[i]) { dist_l[i] = adj[i][j]; } } } for (register int l(0); l < nl; ++l) { for (register int i(0); i < nr; ++i) { slack[i] = 0x7fffffff; } while(true) { memset(vis_l, false, sizeof vis_l ), memset(vis_r, false, sizeof vis_r ); if (BestMatch(l)) break; register int d(0x7fffffff); for (register int i(0); i < nr; ++i){ if (!vis_r[i] && d > slack[i]) { d = slack[i]; } } for (register int i(0); i < nl; ++i){ if (vis_l[i]) { dist_l[i] -= d; } } for (register int i(0); i < nr; ++i) { if (vis_r[i]) dist_r[i] += d; else slack[i] -= d; } } } register int res(0); for (register int i(0); i < nr; ++i) { if(~mate[i]) { res += adj[mate[i]][i]; } } return res; }
最近公共祖先
Heavy path decomposition
学倍增和Tarjan的时候没好好学现在只会树链剖分
题目链接: Luogu P3379 【模板】最近公共祖先(LCA)
时间复杂度:
Dfs1
: \(\Theta(n)\)Dfs2
: \(\Theta(n)\)LowestCommonDivisor
: \(\Theta(\lg n)\)
模板题要写读入优化过
#include <cstdio> #include <vector> #include <algorithm> int f(-1); inline char GetCharacter() { static char buf[2000000], *p1 = buf, *p2 = buf; return (p1 == p2) && (p2 = (p1 = buf) + fread(buf, 1, 2000000, stdin), p1 == p2) ? EOF : *p1++; } #define IS_DIGIT(c) (c >= '0' && c <= '9') inline void Read(int &x) { f = 1, x = 0; static char c = GetCharacter(); while (!IS_DIGIT(c)) { if (c == '-') f = -1; c = GetCharacter(); } while (IS_DIGIT(c)) x = x * 10 + c - '0', c = GetCharacter(); x *= f; } #undef IS_DIGIT std::vector<int> adj[500010]; int heavy[500010], size[500010], father[500010], top[500010], depth[500010]; int root, n, m; int Dfs1(const int &cur, const int &fathernode) { size[cur] = 1; father[cur] = fathernode; depth[cur] = depth[fathernode] + 1; for (auto i : adj[cur]) { if (i != fathernode) { size[cur] += Dfs1(i, cur); if (size[i] > size[heavy[cur]]) heavy[cur] = i; } } return size[cur]; } void Dfs2(const int &cur, const int &topnode) { top[cur] = topnode; if (heavy[cur]) { Dfs2(heavy[cur], topnode); for (auto i : adj[cur]) { if (heavy[cur] != i && father[cur] != i) { Dfs2(i, i); } } } } inline int LowestCommonAncestor(const int &x, const int &y) { int a(x), b(y); while (top[a] != top[b]) { if (depth[top[a]] < depth[top[b]]) std::swap(a, b); a = father[top[a]]; } return depth[a] > depth[b] ? b : a; } int main(int argc, char const *argv[]) { Read(n), Read(m), Read(root); for (int i(1), u, v; i < n; ++i) { Read(u), Read(v); adj[u].push_back(v), adj[v].push_back(u); } Dfs1(root, root); Dfs2(root, root); while (m--) { int u, v; Read(u), Read(v); printf("%d\n", LowestCommonAncestor(u, v)); } return 0; }
最短路
Floyd-Warshall algorithm
枚举每一个结点作为中间点, 再枚举每一个起点和终点, 可以松弛就进行松弛。
时间复杂度: \(\Theta(n^3)\)
题目链接: HDU 2544 最短路
#include <cstdio> #include <cstring> #include <algorithm> int adj[110][110]; int n, m; int main(int argc, char **argv) { while (~scanf("%d %d", &n, &m) && (n || m)) { memset(adj, 0x3f, sizeof(adj)); for (register int i(1), u, v, w; i <= m; ++i) { scanf("%d %d %d", &u, &v, &w); adj[u][v] = adj[v][u] = std::min(adj[u][v], w); } for (register int i(1); i <= n; ++i) adj[i][i] = 0; for (register int k(1); k <= n; ++k) { for (register int i(1); i <= n; ++i) { for (register int j(1); j <= n; ++j) { adj[j][i] = adj[i][j] = std::min(adj[i][j], adj[i][k] + adj[k][j]); } } } printf("%d\n", adj[1][n]); } return 0; }
Shortest Path Faster Algorithm(SPFA)
是Bellman-Ford algorithm的改进, 通常情况下不会出什么问题, 但精心设计的稠密图可以轻易卡掉SPFA, 使用需谨慎。
平均时间复杂度: \(\Theta(|E|)\)
理论上界: \(\Theta(|V||E|)\)
题目链接: Luogu P3371 【模板】单源最短路径(弱化版)
#include <deque> #include <cstdio> #include <vector> #include <cstring> struct Edge { int destination, weight; }; std::vector<Edge> adj[10010]; int n, m, s; int distance[10010]; bool vis[10010]; inline void ShortestPathFasterAlgorithm(); int main(int argc, char const *argv[]) { scanf("%d %d %d", &n, &m, &s); for (register int i(0), u, v, w; i < m; ++i) { scanf("%d %d %d", &u, &v, &w); adj[u].push_back(Edge {v, w}); } ShortestPathFasterAlgorithm(); for (register int i(1); i <= n; ++i) { printf("%d ", distance[i] == 0x3f3f3f3f ? 2147483647 : distance[i]); } return 0; } inline void ShortestPathFasterAlgorithm() { memset(distance, 0x3f, sizeof(distance)); distance[s] = 0; std::deque<int> Q; Q.push_back(s); vis[s] = 0; while (!Q.empty()) { register int cur(Q.front()); Q.pop_front(); vis[cur] = 0; for (auto i : adj[cur]) { if (distance[i.destination] > distance[cur] + i.weight) { distance[i.destination] = distance[cur] + i.weight; if (!vis[i.destination]) { if (distance[i.destination] < distance[Q.front()]) Q.push_front(i.destination); else Q.push_back(i.destination); vis[i.destination] = 1; } } } } }
Dijkstra's algorithm
与最小生成树的Prim's algorithm相像, 主要思想为贪心, 适用于稠密图。
通常情况下使用SPFA, 如果数据卡SPFA可以尝试堆优化的Dijkstra's algorithm。
时间复杂度: \(\Theta((|E|)\lg |E|)\)
题目链接: Luogu P4779 【模板】单源最短路径(标准版)
#include <cstdio> #include <vector> #include <queue> #include <cstring> std::vector<std::pair<int, long long> > adj[100010]; long long distance[100010]; int vis[100010]; int n, m, s; inline void Dijkstra() { register std::priority_queue<std::pair<long long, int>, std::vector<std::pair<long long, int> >, std::greater<std::pair<long long, int> > > Q; memset(distance, 0x3f, sizeof(distance)); distance[s] = 0; Q.push(std::pair<long long, int>(0ll, s)); while (!Q.empty()) { register int cur(Q.top().second); Q.pop(); if (!vis[cur]) { vis[cur] = 1; for (auto i : adj[cur]) { if (distance[i.first] > distance[cur] + i.second) { distance[i.first] = distance[cur] + i.second; Q.push(std::pair<long long, int>(distance[i.first], i.first)); } } } } } int main(int argc, char const *argv[]) { scanf("%d %d %d", &n, &m, &s); for (register int i(1), u, v, w; i <= m; ++i) { scanf("%d %d %d", &u, &v, &w); adj[u].push_back(std::pair<int, long long>(v, (long long)(w))); } Dijkstra(); for (register int i(1); i <= n; ++i) { printf(i == n ? "%lld\n" : "%lld ", distance[i]); } return 0; }
最大流
Edmonds-Karp algorithm
最大流的基础方法, 思想非常简单:
每次BFS找到一条增广路进行增广, 记录该路径上可增广的最大流, 在图中减去即可。、
时间复杂度: \(\Theta(|V||E|^2)\)
题目链接: USACO4.2 Drainage Ditches
#include <cstdio> #include <queue> #include <vector> #include <algorithm> #include <cstring> #define INF 2147483647 int predeccessor[210], flow[210]; int G[210][210]; int n, m; inline int Bfs(const int &S, const int &T) { register std::queue<int> q; memset(predeccessor, -1, sizeof(predeccessor)); predeccessor[S] = 0; flow[S] = INF; q.push(S); while (!q.empty()) { register int current(q.front()); q.pop(); if (current == T) { break; } for (register int i(1); i <= n; ++i) { if (G[current][i] && predeccessor[i] == -1) { predeccessor[i] = current; flow[i] = std::min(flow[current], G[current][i]); q.push(i); } } } return predeccessor[T] == -1 ? -1 : flow[T]; } inline int EdmondsKarp(const int &S, const int &T) { register int ret(0), increase(0); while((increase = Bfs(S, T)) != -1) { register int current(T); while (current != S) { G[predeccessor[current]][current] -= increase; G[current][predeccessor[current]] += increase; current = predeccessor[current]; } ret += increase; } return ret; } int main(int argc, char **argv) { scanf("%d %d", &m, &n); for (register int i(0), u, v, w; i < m; ++i) { scanf("%d %d %d", &u, &v, &w); G[u][v] += w; } printf("%d\n", EdmondsKarp(1, n)); return 0; }
Dinic's algorithm
首先利用BFS对网络进行分层, 然后利用DFS从前一层向后一层反复寻找增广路(利用回溯), 当这次DFS无法继续增广时, 重复BFS步骤, 直到BFS无法到达汇点时, 算法结束。
时间复杂度: \(\Theta(|V|^2 |E|)\)
题目链接: Luogu P3376 【模板】网络最大流
#include <cstdio> #include <cstring> #include <queue> #include <algorithm> int n, m, s, t; int depth[10010]; struct Edge { int destination, maxflow, next; Edge() { destination = maxflow = next = 0; } } edge[200010]; int head[10010]; inline void AddEdge(const int &u, const int &v, const int &w) { static int edge_count(0); edge[edge_count].destination = v; edge[edge_count].maxflow = w; edge[edge_count].next = head[u]; head[u] = edge_count++; } inline bool BreadthFirstSearch(const int &S, const int &T) { register std::queue<int> q; memset(depth, 0, sizeof(depth)); depth[S] = 1; q.push(S); while (!q.empty()) { register int current(q.front()); q.pop(); for (register int i(head[current]); i != -1; i = edge[i].next) { if (edge[i].maxflow && !depth[edge[i].destination]) { depth[edge[i].destination] = depth[current] + 1; q.push(edge[i].destination); } } } return depth[T]; } inline int DepthFirstSearch(const int ¤t, register int maxflow, const int &T) { if (current == T) return maxflow; for (register int i(head[current]); i != -1; i = edge[i].next) { if (depth[edge[i].destination] == depth[current] + 1 && edge[i].maxflow) { register int delta(DepthFirstSearch(edge[i].destination, std::min(maxflow, edge[i].maxflow), T)); if (delta) { edge[i].maxflow -= delta; edge[i ^ 1].maxflow += delta; return delta; } } } return 0; } inline int Dinic(const int &S, const int &T) { register int ret(0), delta; while (BreadthFirstSearch(S, T)) { while ((delta = DepthFirstSearch(S, 0x3f3f3f3f, T))) { ret += delta; } } return ret; } int main(int argc, char **argv) { memset(head, -1, sizeof(head)); scanf("%d %d %d %d", &n, &m, &s, &t); for (register int i(1), u, v, w; i <= m; ++i) { scanf("%d %d %d", &u, &v, &w); AddEdge(u, v, w), AddEdge(v, u, 0); } printf("%d\n", Dinic(s, t)); return 0; }
最小费用最大流
Dinic's algorithm
将费用看作路径长度, 把Dinic's algorithm中的BFS换成SPFA, 每次找费用最小的进行增广
时间复杂度: \(\Theta(|V|^2 |E|)\)
题目链接: Luogu P3381 【模板】最小费用最大流
#include <cstdio> #include <cstring> #include <queue> #include <algorithm> int n, m, s, t; struct Edge { int destination, maxflow, cost, next; Edge() { destination = maxflow = cost = next = 0; } } edge[100010]; int head[5010]; int distance[5010], pre_node[5010], pre_edge[5010], flow[5010], vis[5010]; int maxflow, mincost; inline void AddEdge(const int &u, const int &v, const int &w, const int &c) { static int edge_count(0); edge[edge_count].destination = v; edge[edge_count].maxflow = w; edge[edge_count].cost = c; edge[edge_count].next = head[u]; head[u] = edge_count++; } inline bool ShortestPathFasterAlgorithm(const int &S, const int &T) { memset(distance, 0x3f, sizeof(distance)); memset(flow, 0x3f, sizeof(flow)); memset(vis, 0, sizeof(vis)); register std::queue<int> q; q.push(S); vis[S] = 1, distance[S] = 0, pre_node[T] = -1; while (!q.empty()) { register int current(q.front()); q.pop(); vis[current] = 0; for (register int i(head[current]); i != -1; i = edge[i].next) { if (edge[i].maxflow && distance[edge[i].destination] > distance[current] + edge[i].cost) { distance[edge[i].destination] = distance[current] + edge[i].cost; pre_node[edge[i].destination] = current; pre_edge[edge[i].destination] = i; flow[edge[i].destination] = std::min(flow[current], edge[i].maxflow); if (!vis[edge[i].destination]) { vis[edge[i].destination] = 1; q.push(edge[i].destination); } } } } return pre_node[T] != -1; } inline void Dinic(const int &S, const int &T) { while (ShortestPathFasterAlgorithm(S, T)) { register int current(T); maxflow += flow[T]; mincost += flow[T] * distance[T]; while (current != S) { edge[pre_edge[current]].maxflow -= flow[T]; edge[pre_edge[current] ^ 1].maxflow += flow[T]; current = pre_node[current]; } } } int main(int argc, char **argv) { memset(head, -1, sizeof(head)); scanf("%d %d %d %d", &n, &m, &s, &t); for (register int i(0), u, v, w, c; i < m; ++i) { scanf("%d %d %d %d", &u, &v, &w, &c); AddEdge(u, v, w, c), AddEdge(v, u, 0, -c); } Dinic(s, t); printf("%d %d\n", maxflow, mincost); return 0; }
来源:https://www.cnblogs.com/forth/p/9733509.html