有向图的拓扑排序
cpp
复制代码
//有向图无环图中才有拓扑排序,且都是前面的编号的点指向后面编号的点
#include<iostream>
#include<cstring>
using namespace std;
const int N = 1e5 + 9;
int e[N], ne[N], h[N], idx, n, m, d[N], q[N];
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
bool topsort()
{
int hh = 0, tt = -1;
//将所有入度为0的点入队,编号从1~n
for (int i = 1; i <= n; ++i) if (!d[i]) q[++tt] = i;
while (hh <= tt)
{
int t = q[hh++];//出队
for (int i = h[t]; i != -1; i = ne[i])
{
int j = e[i];
d[j]--;//因为t指向j,又因为t删除了,所以j入度减一
if (!d[j]) q[++tt] = j;
}
}
return tt == (n - 1);//如果每个点都入队了表明为拓扑排序
}
int main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
memset(h, -1, sizeof h);
cin >> n >> m;
for (int i = 0; i < m; ++i)
{
int a, b; cin >> a >> b;
add(a, b);
d[b]++;//入度加一
}
//队列中的顺序刚好就是拓扑排序,排序不唯一
if (topsort()) for (int i = 0; i < n; ++i) cout << q[i] << " ";
else cout << -1;
return 0;
}
dijkstra求最短路
cpp
复制代码
//dijkstra堆优化
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
const int N = 1e5 + 9;
typedef pair<int, int> PII;
int h[N], e[N], ne[N], idx, n, m, dist[N], w[N];
bool st[N];
void add(int x, int y, int z)
{
e[idx] = y, w[idx] = z, ne[idx] = h[x], h[x] = idx++;
}
int dijkstra()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
//first是距离。second是编号
//小根堆
//有更改就加入
//可能加入的点会有冗余,比如1号点一个是10,一个是15,所以用一个st[]数组来标记
priority_queue<PII, vector<PII>, greater<PII>> heap;
heap.push({ 0, 1 });
while (heap.size())
{
//每次取出距离最小的点
auto t = heap.top();
heap.pop();
int ver = t.second, distance = t.first;
//不更新也对,不过会有很多冗余情况考虑
//同一个顶点x,可能在队列中有(dis1, x)、(dis2, x)存在
//而dis1 < dis2,所以(dis1, x)先出队并更新x的邻接顶点
//当(dis2, x)出队的时候,由于dis2 > dis1
//所以x的邻接顶点此时都不会更新,那么(dis2, x)这一步也就冗余了
if (st[ver]) continue;//如果距离已经确定,则跳过该点
st[ver] = true;
for (int i = h[ver]; i != -1; i = ne[i])
{
int j = e[i];
if (dist[j] > distance + w[i])
{
dist[j] = distance + w[i];
heap.push({ dist[j], j });
}
}
}
if (dist[n] == 0x3f3f3f3f) return -1;
return dist[n];
}
int main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
memset(h, -1, sizeof h);
cin >> n >> m;
while (m--)
{
int x, y, z; cin >> x >> y >> z;
add(x, y, z);
}
cout << dijkstra();
return 0;
}
有边数限制的最短路
cpp
复制代码
//bellman_ford()算法
//dijkstra()不能解决负权边最短路问题
#include<iostream>
#include<cstring>
using namespace std;
const int N = 510, M = 1e4 + 9;
//back[]存储上一次的dist[]
int dist[N], back[N], n, m, k;
struct Edge
{
int a, b, w;
} edges[M];
int bellman_ford()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
//存在边数限制,只能用bellman_ford做
//关于为什么循环k次可以保证得到的dist就是相应k条件下的值?
//当明白了备份的作用,就能理解了
//备份的意义就是保证了每条边a->b在每次刷新时a都是独立的
//在遍历过程中dist不会受其他点的影响,只和a上一次的值有关
//而这个备份的操作就是放在k循环下的,所以得到的dist就是相应k条件下的值
for (int i = 0; i < k; ++i)
{
//做备份
//防止发生串联
//串联:由于这个算法的特性决定,
//每次更新得到的必然是在多考虑 1 条边之后能得到的全局的最短路
//而串联指的是一次更新之后考虑了不止一条边:由于使用了松弛
//某节点的当前最短路依赖于其所有入度的节点的最短路
//假如在代码中使用dist[e.b]=min(dist[e.b],dist[e.a] + e.c)
//我们无法保证dist[e.a]是否也在本次循环中被更新,如果被更新了
//并且dist[e.b] > dist[e.a] + e.c
//那么会造成当前节点在事实上"即考虑了一条从某个节点指向a的边
//也考虑了a->b",共两条边
//而使用dist[e.b]=min(dist[e.b],last[e.a] + e.c)
//可以保证a在dist更新后不影响对b的判定,因为后者使用last数组
//保存着上一次循环中的dist的值
memcpy(back, dist, sizeof dist);
for (int j = 0; j < m; ++j)
{
int a = edges[j].a, b = edges[j].b, w = edges[j].w;
dist[b] = min(dist[b], back[a] + w);
}
}
//因为可能存在到n的路径中有负权边存在, 所以导致dist[n]的值小于0x3f3f3f3f
//根据题目给的数据范围最多减去500*10000,也就是500w的值,所以大于一半就好
//注意不要返回-1,因为-1可能就是dist[n]的距离
if (dist[n] > 0x3f3f3f3f >> 1) return -0x3f3f3f3f;
return dist[n];
}
int main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin >> n >> m >> k;
for (int i = 0; i < m; ++i)
{
int x, y, z; cin >> x >> y >> z;
edges[i].a = x, edges[i].b = y, edges[i].w = z;
}
int t = bellman_ford();
if (t == -0x3f3f3f3f) cout << "impossible";
else cout << dist[n];
return 0;
}
spfa求最短路
cpp
复制代码
//spfa
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
const int N = 1e5 + 9;
int h[N], e[N], ne[N], st[N], n, m, idx, w[N], dist[N];
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
int spfa()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
queue<int> q;//存储待更新点
q.push(1);
st[1] = true;
while (q.size())
{
auto t = q.front();
q.pop();
st[t] = false;
for (int i = h[t]; i != -1; i = ne[i])
{
int j = e[i];
if (dist[j] > dist[t] + w[i])
{
dist[j] = dist[t] + w[i];
if (!st[j])
{
q.push(j);
st[j] = true;
}
}
}
}
if (dist[n] == 0x3f3f3f3f) return -0x3f3f3f3f;
return dist[n];
}
int main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
memset(h, -1, sizeof h);
cin >> n >> m;
while (m--)
{
int x, y, z; cin >> x >> y >> z;
add(x, y, z);
}
int t = spfa();
if (t == -0x3f3f3f3f) cout << "impossible";
else cout << t;
return 0;
}
spfa判负环
cpp
复制代码
//spfa判断负环
#include<iostream>
#include<queue>
#include<cstring>
using namespace std;
const int N = 1e4 + 9;
//cnt[]存储点最短路径的边数
int e[N], ne[N], h[N], w[N], dist[N], cnt[N], idx, n, m;
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
bool spfa()
{
memset(dist, 0x3f, sizeof dist);
queue<int> q;
//注意这里是讲所有点都入队,因为负环可能不在1号点这个路径上
//这里dist初不初始化都行,但要是求单点到单点路径上是否存在回路,必须初始化dist
//且dist[s] = 0
for (int i = 1; i <= n; ++i) q.push(i);
while (q.size())
{
auto t = q.front();
q.pop();
for (int i = h[t]; i != -1; i = ne[i])
{
int j = e[i];
if (dist[j] > dist[t] + w[i])
{
dist[j] = dist[t] + w[i];
cnt[j] = cnt[t] + 1;
//边数 >= n,点数 >= n + 1,存在回路
if (cnt[j] >= n) return true;
q.push(j);
}
}
}
return false;
}
int main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
memset(h, -1, sizeof h);
cin >> n >> m;
while (m--)
{
int x, y, z; cin >> x >> y >> z;
add(x, y, z);
}
if (spfa()) cout << "Yes";
else cout << "No";
return 0;
}
多源汇最短路问题-具有多个源点:Floyd求最短路
cpp
复制代码
//Floyd求多源汇最短路
#include<iostream>
#include<cstring>
using namespace std;
const int N = 210, INF = 1e9;
int n, m, Q, d[N][N];
void floyd()
{
for (int k = 1; k <= n; ++k)
{
for (int i = 1; i <= n; ++i)
{
for (int j = 1; j <= n; ++j)
{
d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}
}
}
}
int main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin >> n >> m >> Q;
for (int i = 1; i <= n; ++i)
{
for (int j = 1; j <= n; ++j)
{
if (i == j) d[i][j] = 0;
else d[i][j] = INF;
}
}
while (m--)
{
int a, b, z; cin >> a >> b >> z;
d[a][b] = min(d[a][b], z);//选一个最短的重边
}
floyd();
while (Q--)
{
int a, b; cin >> a >> b;
//因为存在负权边,所以ab之间的距离可能小于无穷
if (d[a][b] > INF / 2) cout << "impossible" << '\n';
else cout << d[a][b] << '\n';
}
return 0;
}
Kruskal算法求最小生成树
cpp
复制代码
//kruskal求最小生成树
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 2e5 + 9;
struct Edge
{
int a, b, w;
bool operator< (const Edge& W) const
{
return w < W.w;
}
} edges[N];
int n, m, p[N], res, cnt;
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
int main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin >> n >> m;
for (int i = 0; i < m; ++i)
{
int a, b, w; cin >> a >> b >> w;
edges[i] = { a, b, 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)
{
res += w;
cnt++;
p[a] = b;//加入集合
}
}
//如果集合中的边数小于n - 1,说明不存在最小生成树
if (cnt < n - 1) cout << "impossible";
else cout << res;
return 0;
}
判定是否是二分图
cpp
复制代码
#include<iostream>
#include<cstring>
using namespace std;
const int N = 100010;
int f[N];
int e[N*2],h[N],ne[N*2],idx;
//数组模拟邻接表模板
void add(int a,int b)
{
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
//并查集find函数模板:找到祖宗节点
int find(int x)
{
if(f[x]!=x) f[x]=find(f[x]);
return f[x];
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
memset(h,-1,sizeof h);
//并查集初始化
for(int i=1;i<=n;i++) f[i]=i;
while(m--)
{
int a,b;
scanf("%d%d",&a,&b);
add(a,b);
add(b,a);
}
//遍历每一个点
for(int u=1;u<=n;u++)
{
//遍历当前点的邻接点
for(int i=h[u];~i;i=ne[i])
{
int j=e[i];
//如果邻接点和当前节点属于同一集合,不满足二分,直接NO
if(find(j)==find(u))
{
puts("No");
return 0;
}
//将所有邻接点合并到同一个集合
f[find(e[h[u]])]=find(j);
}
}
puts("Yes");
return 0;
}
二分图的最大匹配
cpp
复制代码
//二分图的最大匹配
#include<iostream>
#include<cstring>
using namespace std;
const int N = 510, M = 1e5 + 9;
int h[N], e[M], ne[M], n1, n2, match[N], m, idx, res;
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] = true;
//如果这个姑娘还没有被匹配过,或者找到这个男生喜欢别的姑娘
if (match[j] == 0 || find(match[j]))
{
match[j] = x;
return true;
}
}
}
return false;
}
int main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
memset(h, -1, sizeof h);
cin >> n1 >> n2 >> m;
while (m--)
{
int a, b; cin >> a >> b;
//不需要存双向边
add(a, b);
}
for (int i = 1; i <= n1; ++i)
{
//每次将姑娘全初始化为没有遍历,因为之前别人喜欢的姑娘我也可能喜欢
memset(st, false, sizeof st);
if (find(i)) res++;//如果找到数量就加一
}
cout << res;
return 0;
}