1、大纲
n是点,m是边
染色法是判断一个图是不是二分图(dfs)
2、最小生成树
2.1、Prim
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 510, INF = 0x3f3f3f3f;
int n, m;
//prim算法适合稠密图,加上本题也是稠密图
int g[N][N];
//每个点到集合的距离
//集合->找到集合最小值的点组成的连通图
int dist[N];
//是否在集合中
bool st[N];
int prim()
{
memset(dist, 0x3f, sizeof dist);
//最小生成树中,所有边的权重之和
int res = 0;
for (int i = 0; i < n; i ++ )
{
//每一次循环开始 都让t=-1。
int t = -1;
for (int j = 1; j <= n; j ++ )
//在集合外(第一个点或者是到集合距离最小的点)
if (!st[j] && (t == -1 || dist[t] > dist[j]))
//当前到集合距离最小的点
t = j;
st[t] = true;
//i 判断是否为第一个点 第一个点的dist[t]=正无穷
//不是第一个点且图是不联通的(不存在最小生成树),返回无穷大
if (i && dist[t] == INF) return INF;
//不是第一个点
if (i) res += dist[t];
//使用刚插入集合的点,更新其它点到集合的距离
for (int j = 1; j <= n; j ++ )
if(!st[j])
dist[j] = min(dist[j], g[t][j]);
}
return res;
}
int main()
{
scanf("%d%d", &n, &m);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(i==j) g[i][j]=0;
else g[i][j]=INF;
while (m -- )
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
//无向图,要添加两个方向的边,有重边选最小的边。
g[a][b] = g[b][a] = min(g[a][b], c);
}
int t = prim();
//当所有点不联通的时候,就不存在生成树
if (t == INF) puts("impossible");
else printf("%d\n", t);
return 0;
}
最小生成树是没有环的,正边和负边都可以。
2.2、Kruskal
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010, M = 200010, INF = 0x3f3f3f3f;
int n, m;
//并查集中的p[N]
int p[N];
//算法本身不需要用复杂的数据结构存储边
struct Edge
{
int a, b, w;
//重载结构体,用sort实现edge从小到大的排序 =
bool operator< (const Edge &W)const
{
return w < W.w;
}
}edges[M];
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
int kruskal()
{
sort(edges, edges + m);
for (int i = 1; i <= n; i ++ ) p[i] = i; // 初始化并查集
//res存储最小生成树权重之和,cnt当前加了多少边
int res = 0, cnt = 0;
//从小到大枚举所有边
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;
res += w;
cnt ++ ;
}
}
//n个点 n-1条边
if (cnt < n - 1) return INF;
return res;
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 0; i < m; i ++ )
{
int a, b, w;
scanf("%d%d%d", &a, &b, &w);
edges[i] = {a, b, w};
}
int t = kruskal();
if (t == INF) puts("impossible");
else printf("%d\n", t);
return 0;
}
3、二分图
3.1、染色法判定二分图
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
//无向图,存两条有向边
const int N = 100010, M = 200010;
int n, m;
int h[N], e[M], ne[M], idx;
int color[N];
void add(int a, int b)
{
e[idx] = b, 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])
{
//染色 失败时返回false
if (!dfs(j, 3 - c)) return false;
}
//不需要染色 判断叶子与父亲是否一致 一致就失败
else if (color[j] == c) return false;
}
return true;
}
int main()
{
scanf("%d%d", &n, &m);
memset(h, -1, sizeof h);
while (m -- )
{
int a, b;
scanf("%d%d", &a, &b);
add(a, b), add(b, a);
}
bool flag = true;
for (int i = 1; i <= n; i ++ )
//如果当前点没有染色,就进行染色
if (!color[i])
{
//返回false就不是二分图
if (!dfs(i, 1))
{
flag = false;
break;
}
}
if (flag) puts("Yes");
else puts("No");
return 0;
}
3.2、匈牙利算法
3.2.1 二分图最大匹配
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 510, M = 100010;
int n1, n2, m;
int h[N], e[M], ne[M], idx;
//右边的妹子和哪个男生在一起
int 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])
{
//考虑过的设置为true
st[j] = true;
//当这个妹子没有匹配,或者匹配的有下家
/*
find(match[j]) 的执行思路:
match[j]对应的是j号女生牵手的男生w(假设)。
首先对于x考虑过的女生,w不再考虑,因为这些女生已经牵手
w需要考虑x没考虑过的(且w喜欢的女生)。
当找到女生,才把j让给x
*/
if (match[j] == 0 || find(match[j]))
{
//与此男子匹配并返回true
match[j] = x;
return true;
}
}
}
return false;
}
int main()
{
scanf("%d%d%d", &n1, &n2, &m);
memset(h, -1, sizeof h);
//虽然为无向图,但是只需要考虑从n1->n2,存储一条遍就可以。
while (m -- )
{
int a, b;
scanf("%d%d", &a, &b);
add(a, b);
}
//当前匹配的数量
int res = 0;
//依次分析每个男生找哪一个妹子
for (int i = 1; i <= n1; i ++ )
{
//男子考虑的妹子置为空 保证对一个男子每个妹子考虑一遍
memset(st, false, sizeof st);
//find返回bool类型
if (find(i)) res ++ ;
}
printf("%d\n", res);
return 0;
}
左边当前匹配的这个点已经属于其它点,看其它点能否换一个点匹配,将这个点让给当前点。