图论总结 Dijkstra Tarjan 最小生成树 二分图 最短路 强连通分量 双连通分量 Bellman-Ford SPFA 二分图染色 Kruskal Prim 网络流 二分图匹配 Dinic
2017-09-15 19:09
836 查看
这周学些图论。
Dijkstra
SCC
BCC
Bipartite
Kruskal
(Prim)
Bellman-Ford
Dinic
以及一些常用技巧。
首先讲Dijkstra
一般的Dijkstra就是堆优化对吧
算单源最短路的时间复杂度是O(nlogn)
完整代码。
比如一个图有一些边,给定st(start)和ed(end),但你可以选择一条路,经过它的花费是K。求最短路。
常用的做法是从st做一次Dijkstra,然后从ed做一次反的Dijkstra。这样每个节点就有两个值,到st的距离和到ed的距离。然后枚举每一条边,找每一条边两端点到st、ed的和加上K的最小值。
Dijkstra求最短路还可以加上一些限制。改一下到st距离的意义,然后对入队条件加一些限制即可。heap里两个元素不变。
一个猜想:只要是有给定st点和ed点的问题,如果可以通过从st出发DFS到ed找出所有边,并对经过的边这个数组经过向一边扫描处理可以得到答案的问题,都可以用Dijkstra做。
SCC强连通分量
主要用处是缩点,去掉有向图中的环变成DAG,然后就可以用DP乱整了。
BCC双连通分量
注意stack中存的是边
Bipartite二分图染色
判断图是不是二分图。这个遍历邻边的方法在很多都有用,比如2-SAT等。
Kruskal
限于篇幅,不贴代码了。这里讲几个应用。
最小瓶颈路:求无向图中u到v的一条路径,使经过的边权最大值最小。
方法:求出最小生成树,则u到v在树上的路径就是所求路径。
每对结点间的最小瓶颈路:求无向图中每两个节点间的最小瓶颈路大小。
方法:先求出最小生成树。然后选定一个点为根节点,跑DFS。对于每对结点间的所求值,取为其父亲节点和定点的值与父亲节点与子节点距离的较大值。
次小生成树。
方法:求最小生成树,枚举添加哪一条新边。这样会出现一条回路。那么要删除的边在添加边两端点的路径上的 最长边。这样求出每对结点间的最大边权,求法与上一个方法类似(不同)。时间复杂度O(n2)。
二分图匹配
标准做法是Hungarian,但这个不好。用Dinic更好。
应用:
一般来说,题目不会直接给出明显的二分图,甚至连图都没有。但你只要发现要求是每两个什么什么怎么样,就可以想到用二分图匹配。
给定n*n的01矩阵,问能否通过交换整行或整列的办法,使得左上到右下的对角线上都是1。
建立二分图,一半是行编号(1~n),一半是列编号(n+1~2n)
对于每一个1,连接所在行和所在列的结点。然后求最大匹配数是否等于n即可。
网络流
Dinic
先贴个模版
#include <vector>
#include <iostream>
#include <cstring>
#include <queue>
#include <algorithm>
using namespace std;
const int maxn = 100050;
const int maxm = 1000050;
const int inf = 1e9+7;
struct edge{
int from, to, cap, flow;
};
vector<edge> E;
vector<int> G[maxn];
int n, m, s, t;
int cur[maxn], d[maxn], vis[maxn];
void addedge(int from, int to, int cap) {
E.push_back((edge){from, to, cap, 0});
E.push_back((edge){to, from, 0, 0});
int m = E.size();
G[from].push_back(m-2);
G[to].push_back(m-1);
}
int enlevel() {
memset(vis, 0, sizeof(vis));
queue<int> Q;
Q.push(s);
d[s] = 0;
vis[s] = 1;
while(!Q.empty()) {
int x = Q.front(); Q.pop();
for(int i = 0; i < G[x].size(); i++) {
edge& e = E[G[x][i]];
if(!vis[e.to] && e.cap > e.flow) {
vis[e.to] = 1;
d[e.to] = d[x] + 1;
Q.push(e.to);
}
}
}
return vis[t];
}
int large(int x, int a) {
if(x == t || a == 0) return a;
int flow = 0, f;
for(int& i = cur[x]; i < G[x].size(); i++) {
edge& e = E[G[x][i]];
if(d[x] + 1 == d[e.to]) if(f = large(e.to, min(a, e.cap-e.flow)) > 0) {
e.flow += f;
E[G[x][i^1]].flow -= f;
flow += f;
a -= f;
if(a == 0) break;
}
}
return flow;
}
int maxflow() {
int flow = 0;
while(enlevel()) {
memset(cur, 0, sizeof(cur));
flow += large(s, inf);
}
return flow;
}
int main() {
cin >> n >> m >> s >> t;
for(int i = 0; i < m; i++) {
int from, to, cap;
cin >> from >> to >> cap;
addedge(from - 1, to - 1, cap - 1);
}
cout << maxflow();
return 0;
}
相关内容很多,但现在不讲。
现在讲一种方法:
拆点法
拆点在很多算法中都非常有用。
具体地说,如果直接套摸版没有办法,或者每条边的权值是不定的,或者有各种限制条件等等,都可以尝试用拆点。
题目是不会让你改写模版的,因此模版的基础变形是最重要的。到灵活变形时,就不需要靠积累了。
图论大概NOIP考的有这些算法:
Dijkstra
SCC
BCC
Bipartite
Kruskal
(Prim)
Bellman-Ford
Dinic
以及一些常用技巧。
首先讲Dijkstra
一般的Dijkstra就是堆优化对吧
算单源最短路的时间复杂度是O(nlogn)
完整代码。
#include <vector> #include <queue> #include <cstring> #include <cstdio> using namespace std; const int maxn = 100050; const int inf = 1e9; struct edge{ int to, dist; }; struct heap{ int d, u; bool operator < (const heap& rhs) const { return d > rhs.d; } }; vector<edge> G[maxn]; bool vis[maxn]; int dj[maxn], pre[maxn]; int n, m; void dijkstra(int st) { priority_queue<heap> Q; for(int i = 0; i < n; i++) dj[i] = inf; dj[st] = 0; memset(vis, 0, sizeof(vis)); Q.push((heap){0, st}); while(!Q.empty()) { heap j = Q.top(); Q.pop(); int u = j.u; if(vis[u]) continue; vis[u] = 1; for(int i = 0; i < G[u].size(); i++) { int v = G[u][i].to, w = G[u][i].dist; if(dj[v] > dj[u] + w) { dj[v] = dj[u] + w; pre[v] = u; Q.push((heap){dj[v], v}); } } } } int main() { scanf("%d%d", &n, &m); for(int i = 0; i < m; i++) { int from, to, dist; scanf("%d%d%d", &from, &to, &dist); edge e; e.to = to - 1; e.dist = dist; G[from - 1].push_back(e); } dijkstra(0); for(int i = 0; i < n; i++) printf("%d ", dj[i]); return 0; }讲几个算法
比如一个图有一些边,给定st(start)和ed(end),但你可以选择一条路,经过它的花费是K。求最短路。
常用的做法是从st做一次Dijkstra,然后从ed做一次反的Dijkstra。这样每个节点就有两个值,到st的距离和到ed的距离。然后枚举每一条边,找每一条边两端点到st、ed的和加上K的最小值。
Dijkstra求最短路还可以加上一些限制。改一下到st距离的意义,然后对入队条件加一些限制即可。heap里两个元素不变。
一个猜想:只要是有给定st点和ed点的问题,如果可以通过从st出发DFS到ed找出所有边,并对经过的边这个数组经过向一边扫描处理可以得到答案的问题,都可以用Dijkstra做。
SCC强连通分量
#include <iostream> #include <vector> #include <stack> #include <cstring> #include <algorithm> using namespace std; const int maxn = 100050; vector<int> G[maxn]; stack<int> S; int n, m, dcl, sccn; int sccno[maxn], pre[maxn]; int getlow(int u) { int lowu = pre[u] = ++dcl; S.push(u); for(int i = 0; i < G[u].size(); i++) { int v = G[u][i]; if(!pre[v]) { int lowv = getlow(v); lowu = min(lowu, lowv); }else if(!sccno[v]) lowu = min(lowu, pre[v]); } if(lowu == pre[u]) { sccn++; for(;;) { int j = S.top(); S.pop(); sccno[j] = sccn; if(u == j) break; } } return lowu; } void find_scc() { dcl = sccn = 0; memset(sccno, 0, sizeof(sccno)); memset(pre, 0, sizeof(pre)); for(int i = 0; i < n; i++) if(!pre[i]) getlow(i); } int main() { cin >> n >> m; for(int i = 0; i < m; i++) { int from, to; cin >> from >> to; G[from - 1].push_back(to - 1); } find_scc(); for(int i=0;i<n;i++)cout<<sccno[i]& 4000 lt;<" "; cout<<endl; }
主要用处是缩点,去掉有向图中的环变成DAG,然后就可以用DP乱整了。
BCC双连通分量
注意stack中存的是边
#include <vector> #include <stack> #include <cstring> #include <algorithm> #include <cstdio> using namespace std; const int maxn = 100050; struct edge{ int from, to; }; vector<int> G[maxn]; stack<edge> S; int dcl, bccn, pre[maxn], bccno[maxn], iscut[maxn]; int n, m; int getlow(int u, int fa) { int lowu = pre[u] = ++dcl; int child = 0; for(int i = 0; i < G[u].size(); i++) { int v = G[u][i]; if(!pre[v]) { S.push((edge){u, v}); child++; int lowv = getlow(v, u); lowu = min(lowu, lowv); if(lowv >= pre[u]) { iscut[u] = 1; bccn++; for(;;) { edge j = S.top(); S.pop(); bccno[j.from] = bccn; bccno[j.to] = bccn; if(j.from == u && j.to == v) break; } } }else if(pre[v] < pre[u] && v != fa) { S.push((edge){u, v}); lowu = min(lowu, pre[v]); } } if(fa < 0 && child == 1) iscut[u] = 0; return lowu; } void find_bcc() { dcl = bccn = 0; memset(pre, 0, sizeof(pre)); memset(bccno, 0, sizeof(bccno)); memset(iscut, 0, sizeof(iscut)); for(int i = 0; i < n; i++) if(!pre[i]) getlow(i, -1); } int main() { scanf("%d%d", &n, &m); for(int i = 0; i < m; i++) { int from, to; scanf("%d%d", &from, &to); G[from - 1].push_back(to - 1); G[to - 1].push_back(from - 1); } find_bcc(); for(int i = 0; i < n; i++) printf("%d ", bccno[i]); printf("\n"); return 0; }没什么好讲的。
Bipartite二分图染色
判断图是不是二分图。这个遍历邻边的方法在很多都有用,比如2-SAT等。
#include <vector> #include <cstdio> #include <iostream> using namespace std; const int maxn = 100050; vector<int> G[maxn]; int col[maxn]; int n, m; int read() { int a = 0, c; do c = getchar(); while(c < 48 || c > 57); do{a = a * 10 + c - 48; c = getchar();} while(c > 47 && c < 58); return a; } int bipartite(int u) { for(int i = 0; i < G[u].size(); i++) { int v = G[u][i]; if(col[v] == col[u]) return 0; if(!col[v]) { col[v] = 3 - col[u]; if(!bipartite(v)) return 0; } } return 1; } int main() { n = read(); m = read(); for(int i = 0; i < m; i++) { int from = read() - 1, to = read() - 1; G[from].push_back(to); // G[to].push_back(from); } col[0] = 1; cout << bipartite(0) << endl; for(int i = 0; i < n; i++) cout << col[i] << " "; cout << endl; return 0; }另外,虽然我用的都是vector存邻接表,但据说经典的样式更好。
Kruskal
限于篇幅,不贴代码了。这里讲几个应用。
最小瓶颈路:求无向图中u到v的一条路径,使经过的边权最大值最小。
方法:求出最小生成树,则u到v在树上的路径就是所求路径。
每对结点间的最小瓶颈路:求无向图中每两个节点间的最小瓶颈路大小。
方法:先求出最小生成树。然后选定一个点为根节点,跑DFS。对于每对结点间的所求值,取为其父亲节点和定点的值与父亲节点与子节点距离的较大值。
次小生成树。
方法:求最小生成树,枚举添加哪一条新边。这样会出现一条回路。那么要删除的边在添加边两端点的路径上的 最长边。这样求出每对结点间的最大边权,求法与上一个方法类似(不同)。时间复杂度O(n2)。
二分图匹配
标准做法是Hungarian,但这个不好。用Dinic更好。
应用:
一般来说,题目不会直接给出明显的二分图,甚至连图都没有。但你只要发现要求是每两个什么什么怎么样,就可以想到用二分图匹配。
给定n*n的01矩阵,问能否通过交换整行或整列的办法,使得左上到右下的对角线上都是1。
建立二分图,一半是行编号(1~n),一半是列编号(n+1~2n)
对于每一个1,连接所在行和所在列的结点。然后求最大匹配数是否等于n即可。
网络流
Dinic
先贴个模版
#include <vector>
#include <iostream>
#include <cstring>
#include <queue>
#include <algorithm>
using namespace std;
const int maxn = 100050;
const int maxm = 1000050;
const int inf = 1e9+7;
struct edge{
int from, to, cap, flow;
};
vector<edge> E;
vector<int> G[maxn];
int n, m, s, t;
int cur[maxn], d[maxn], vis[maxn];
void addedge(int from, int to, int cap) {
E.push_back((edge){from, to, cap, 0});
E.push_back((edge){to, from, 0, 0});
int m = E.size();
G[from].push_back(m-2);
G[to].push_back(m-1);
}
int enlevel() {
memset(vis, 0, sizeof(vis));
queue<int> Q;
Q.push(s);
d[s] = 0;
vis[s] = 1;
while(!Q.empty()) {
int x = Q.front(); Q.pop();
for(int i = 0; i < G[x].size(); i++) {
edge& e = E[G[x][i]];
if(!vis[e.to] && e.cap > e.flow) {
vis[e.to] = 1;
d[e.to] = d[x] + 1;
Q.push(e.to);
}
}
}
return vis[t];
}
int large(int x, int a) {
if(x == t || a == 0) return a;
int flow = 0, f;
for(int& i = cur[x]; i < G[x].size(); i++) {
edge& e = E[G[x][i]];
if(d[x] + 1 == d[e.to]) if(f = large(e.to, min(a, e.cap-e.flow)) > 0) {
e.flow += f;
E[G[x][i^1]].flow -= f;
flow += f;
a -= f;
if(a == 0) break;
}
}
return flow;
}
int maxflow() {
int flow = 0;
while(enlevel()) {
memset(cur, 0, sizeof(cur));
flow += large(s, inf);
}
return flow;
}
int main() {
cin >> n >> m >> s >> t;
for(int i = 0; i < m; i++) {
int from, to, cap;
cin >> from >> to >> cap;
addedge(from - 1, to - 1, cap - 1);
}
cout << maxflow();
return 0;
}
相关内容很多,但现在不讲。
现在讲一种方法:
拆点法
拆点在很多算法中都非常有用。
具体地说,如果直接套摸版没有办法,或者每条边的权值是不定的,或者有各种限制条件等等,都可以尝试用拆点。
题目是不会让你改写模版的,因此模版的基础变形是最重要的。到灵活变形时,就不需要靠积累了。
相关文章推荐
- 浅析最小生成树和单源最短路径的区别(含Prim、Kruskal、Dijkstra、Bellman-Ford)
- 稠密图(邻接矩阵),并查集,最短路径(Dijkstra,spfa),最小生成树(kruskal,prim)
- 最短路知识点总结(Dijkstra,Floyd,SPFA,Bellman-Ford)
- 稀疏图(邻接链表),并查集,最短路径(Dijkstra,spfa),最小生成树(kruskal,prim)
- 最短路知识点总结(Dijkstra,Floyd,SPFA,Bellman-Ford)
- 最短路知识点总结(Dijkstra,Floyd,SPFA,Bellman-Ford)
- 图论总结,查分约束系统讲解,最短路,最小生成树,二分图
- hdu 2544 【总结】 Dijkstra,Bellman-Ford ,SPFA 最短路求法及对应优化
- hdu 2544 【总结】 Dijkstra,Bellman-Ford ,SPFA 最短路求法及对应优化
- 图论--最小生成树总结(Prim&&Kruskal)
- Floyd Dijkstra Bellman-Ford spfa 四种最短路经典算法汇总 HDU 2544为例
- HDU 2544 最短路(四种写法:Floyd、Dijkstra、Bellman-Ford、SPFA)
- 带权最短路 Dijkstra, SPFA, Bellman-Ford, ASP, Floyd-Warshall 算法分析
- [图论] 最短路径(Bellman-Ford , SPFA , Floyed , Dijkstra)
- 带权最短路 Dijkstra, SPFA, Bellman-Ford, ASP, Floyd-Warshall 算法分析
- C++实现矩阵图的遍历·最小生成树(prim,kruskal)·最短路径(Dijkstra,floyd)
- 图论 之 最小生成树 (Kruskal and Prim)
- HDU-#2544 最短路(Dijkstra、Floyd、Bellman-Ford、SPFA)
- POJ 3259 Wormholes (图论---最短路 Bellman-Ford || SPFA)
- 最短路模板(Dijkstra & Dijkstra算法+堆优化 & bellman_ford & 单源最短路SPFA)