最短路 二分图 模板
Posted 317zhang
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了最短路 二分图 模板相关的知识,希望对你有一定的参考价值。
最小生成树 (正边,负边都可以)
Prim 朴素版 O(n^2)
- 稠密图,代码短,跟Dij很相似
算法流程(以点来扩展):
初始化dist [i] 为 正无穷
for i 0 ~ n
? 先集合外距离最近的点 赋值给 t
? 用 t 更新 其他点到 集合 的距离
? st[t] = true;
? * 其中 ,点 到 集合的距离,定义为 ,集合外一点,到集合内的点的距离,最小的那条边
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 510,INF = 0x3f3f3f3f;
int g[N][N],dist[N],n,m;
bool st[N];
int prim()
int ret = 0;
memset(dist,0x3f,sizeof dist);
for(int i = 0;i < n; ++i)
int t = -1;
for(int j = 1;j <= n; ++j) // 选取集合外一点,到集合内点距离最小的点
if(!st[j] && (t == -1 || dist[j] < dist[t]))
t = j;
st[t] = 1;
if(i && dist[t] == INF) return -1; // 如果 当前选的最小的点为INF,说明不连通,第一次除外
// 因为 第一次 集合中没有元素
if(i) ret += dist[t];// 除了第一次,之后的每一次,dist t 都代表一条边
// 先累加,在更新,(不然就会被 负的自环搞崩, 例如 1-1 ,权值为 -10)
for(int j = 1;j <= n; ++j) dist[j] = min(dist[j],g[t][j]); // 因为 t 已经被选到集合中去了
// 这个for的意思就是, 其他点能否通过 集合中的 t 点 缩短,j点到集合的距离
return ret;
int main()
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
int u,v,w;
cin >> n >> m;
for(int i = 0;i <= n; ++i)
for(int j = 0;j <= n; ++j)
g[i][j] = g[j][i] = INF;
while(m--)
cin >> u >> v >> w;
g[u][v] = g[v][u] = min(g[u][v],w);
int ans = prim();
cout << ans ;
return 0;
Kruskal O(mlogm)
- 稀疏图
算法流程(以边来扩展,并查集的思想)
- 将所有边,按权重,从小到大排序 (sort)
- 枚举每条边 a -> b ,权重 c
- 如果 a 和 b 不连通,将a -> b 这条边 加入集合中
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int M = 2e5 + 10,N = 1e5 + 10;
int p[N],n,m;
struct Edge
int a,b,w;
bool operator < (const Edge & W) const
return w < W.w;
edges[M];
int find(int x)
if(x != p[x]) p[x] = find(p[x]);
return p[x];
int main()
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
int cnt = 0,ret = 0;
cin >> n >> m;
for(int i = 0;i < m; ++i)
cin >> edges[i].a >> edges[i].b >> edges[i].w;
sort(edges,edges + m);
for(int i = 1;i <= n; ++i) p[i] = i;
for(int i = 0;i < m; ++i)
int a = edges[i].a,b = edges[i].b,w = edges[i].w;
a = find(a),b = find(b);
if(a != b)
p[a] = b;
ret += w;
cnt ++;
if(cnt < n-1) cout << "impossible";
else cout << ret ;
return 0;
二分图
染色法 (用DFS判定二分图) O(m+n)
性质:
一个图是二分图 当且仅当 图中不含 奇数环
环: 从一个点出发,又回到出发点的路径
奇数环 : 环当中边的数量 是 奇数
so, 如果一个图中有奇数环,那么它一定不是二分图
证明:环当中的第一个点属于左边,第二个点,一定属于右边,。。依次类推
? 假如环是奇数,我们可以推出,出发点属于 右边,与已知矛盾。。
算法流程:
for i 1 ~ n
如果 i 未染色
dfs(i) ,把 i 所在的连通块都染色一遍
#include <iostream>
#include <cstring>
using namespace std;
const int N = 1e5 + 10;
int e[N],ne[N],w[N],h[N],idx,n,m;
int color[N];
void add(int a,int b,int c)
e[idx] = b;
w[idx] = c;
ne[idx] = h[a];
h[a] = idx ++;
bool dfs(int u,int c)
color[u] = c;
for(int i = h[u];i != -1;i = ne[i])
int j = e[i];
if(!color[j])
if(!dfs(j,3-c)) return 0;
else if(color[j] == c) return 0;
return 1;
int main()
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
memset(h,-1,sizeof h);
int a,b,c;
cin >> n >> m;
for(int i = 0;i < m; ++i)
cin >> a >> b >> c;
add(a,b,c);
add(b,a,c);
bool f = 1;
for(int i = 1;i <= n; ++i)
if(!color[i] && !dfs(i,1))
f = 0;
break;
if(f) cout << "Yes";
else cout <<"No";
return 0;
匈牙利算法(求最大匹配) O(m+n) 实际运行时间远小于 O(mn)
算法流程:
- for i 1 ~ n 依次看每个男生(左边的点)
- 先把所有妹子的标记 st 清空 (把右边的标记点集情况)
- 如果 find(i) 返回为 真 ,res ++
- find(x)
- 枚举该男生看上的女生 j(枚举当前左边点 连向的右边点)
- 如果当前的妹子没有被选过
- 当前妹纸标记被选
- 如果当前妹纸没有匹配男生 或者 该妹子已经匹配的男生可以找到下家的话(这样妹纸就空出来了)
- 标记匹配数组 match [j] = i;
- 如果当前的妹子没有被选过
- 最后没找到的话,就返回 false
- 枚举该男生看上的女生 j(枚举当前左边点 连向的右边点)
#include <iostream>
#include <cstring>
using namespace std;
const int N = 510,M = 1e5 + 10;
int n1,n2,m;
int h[N],e[M],ne[M],idx,match[N];
bool st[N];
void add(int a,int b)
e[idx] = b;
ne[idx] = h[a];
h[a] = idx ++;
bool find(int x)
for(int i = h[x];i != -1;i = ne[i])
int j = e[i];
if(!st[j])
st[j] = 1;
if(match[j] == 0 || find(match[j]))
match[j] = x;
return 1;
return 0;
int main()
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
int u,v,res = 0;
cin >> n1 >> n2 >> m;
memset(h,-1,sizeof h);
while(m--)
cin >> u >> v;
add(u,v);
for(int i = 1;i <= n1; ++i)
memset(st,0,sizeof st);
if(find(i)) res ++;
cout << res;
return 0;
以上是关于最短路 二分图 模板的主要内容,如果未能解决你的问题,请参考以下文章
Glorious Brilliance (最短路 + 带权二分图匹配)