文章目录
以下代码均来自ACWing
作者:yxc
链接: https://www.acwing.com
求a, b的公共祖先(LCA)
vector<int> v[40010];
int depth[40010], fa[40010][16]; // 16是根据节点数量 log(40000) = 16
void bfs(int root)
{
memset(depth, 0x3f, sizeof depth);
depth[0] = 0, depth[root] = 1;
queue<int> q;
q.push(root);
while(q.size()) {
auto t = q.front(); q.pop();
for(int i = 0; i < v[t].size(); i ++) {
int j = v[t][i];
if(depth[j] > depth[t] + 1) {
depth[j] = depth[t] + 1;
q.push(j);
fa[j][0] = t;
for(int k = 1; k <= 15; k ++) {
fa[j][k] = fa[fa[j][k - 1]][k - 1];
}
}
}
}
}
int lca(int a, int b)
{
if(depth[a] < depth[b]) swap(a, b);
for(int k = 15; k >= 0; k --) {
if(depth[fa[a][k]] >= depth[b]) {
a = fa[a][k];
}
}
if(a == b) return a;
for(int k = 15; k >= 0; k --) {
if(fa[a][k] != fa[b][k]) {
a = fa[a][k], b = fa[b][k];
}
}
return fa[a][0];
}
void solve()
{
int n = read(); //读入n个点
int root = 0;
for(int i = 0; i < n; i ++) {
int a = read(), b = read(); //存图
if(b == -1) root = a;
else v[a].push_back(b), v[b].push_back(a);
}
bfs(root);
int m = read(); //m次查询
while(m --) {
int a = read(), b = read(); //查询a, b的公共祖先
int p = lca(a, b); // 返回a, b的公共祖先
if (p == a) puts("1");
else if (p == b) puts("2");
else puts("0");
}
}
求一个树中任意两点之间的距离(Tarjan)
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;
const int N = 10010, M = N * 2;
int n, m;
int h[N], e[M], w[M], ne[M], idx; //邻接表存图
int dist[N]; // 计算距离
int p[N]; // 并查集
int res[M]; // res[i] = 第i个查询的答案
int st[N]; // 标记点是否被遍历过
vector<PII> query[N]; // first存查询的另外一个点,second存查询编号
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
void dfs(int u, int fa)
{
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if (j == fa) continue;
dist[j] = dist[u] + w[i];
dfs(j, u);
}
}
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
void tarjan(int u)
{
st[u] = 1;
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if (!st[j])
{
tarjan(j);
p[j] = u;
}
}
for (auto item : query[u])
{
int y = item.first, id = item.second;
if (st[y] == 2)
{
int anc = find(y);
res[id] = dist[u] + dist[y] - dist[anc] * 2;
}
}
st[u] = 2;
}
int main()
{
scanf("%d%d", &n, &m); // n个点,m个问题
memset(h, -1, sizeof h);
for (int i = 0; i < n - 1; i ++ )
{
int a, b, c; // a与b连接,权值是c
scanf("%d%d%d", &a, &b, &c);
add(a, b, c), add(b, a, c);
}
for (int i = 0; i < m; i ++ )
{
int a, b; //查询a与b之间的距离
scanf("%d%d", &a, &b);
if (a != b)
{
query[a].push_back({b, i});
query[b].push_back({a, i});
}
}
for (int i = 1; i <= n; i ++ ) p[i] = i;
dfs(1, -1);
tarjan(1);
for (int i = 0; i < m; i ++ ) printf("%d\n", res[i]);
return 0;
}
求次小生成树
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 100010, M = 300010, INF = 0x3f3f3f3f;
int n, m;
struct Edge
{
int a, b, w;
bool used;
bool operator< (const Edge &t) const
{
return w < t.w;
}
}edge[M]; //存链表
int p[N]; // 并查集
int h[N], e[M], w[M], ne[M], idx; // 存邻接表
int depth[N], fa[N][17], d1[N][17], d2[N][17]; //求最近公共祖先
int q[N];
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
LL kruskal()
{
for (int i = 1; i <= n; i ++ ) p[i] = i;
sort(edge, edge + m);
LL res = 0;
for (int i = 0; i < m; i ++ )
{
int a = find(edge[i].a), b = find(edge[i].b), w = edge[i].w;
if (a != b)
{
p[a] = b;
res += w;
edge[i].used = true;
}
}
return res;
}
void build()
{
memset(h, -1, sizeof h);
for (int i = 0; i < m; i ++ )
if (edge[i].used)
{
int a = edge[i].a, b = edge[i].b, w = edge[i].w;
add(a, b, w), add(b, a, w);
}
}
void bfs()
{
memset(depth, 0x3f, sizeof depth);
depth[0] = 0, depth[1] = 1;
q[0] = 1;
int hh = 0, tt = 0;
while (hh <= tt)
{
int t = q[hh ++ ];
for (int i = h[t]; ~i; i = ne[i])
{
int j = e[i];
if (depth[j] > depth[t] + 1)
{
depth[j] = depth[t] + 1;
q[ ++ tt] = j;
fa[j][0] = t;
d1[j][0] = w[i], d2[j][0] = -INF;
for (int k = 1; k <= 16; k ++ )
{
int anc = fa[j][k - 1];
fa[j][k] = fa[anc][k - 1];
int distance[4] = {d1[j][k - 1], d2[j][k - 1], d1[anc][k - 1], d2[anc][k - 1]};
d1[j][k] = d2[j][k] = -INF;
for (int u = 0; u < 4; u ++ )
{
int d = distance[u];
if (d > d1[j][k]) d2[j][k] = d1[j][k], d1[j][k] = d;
else if (d != d1[j][k] && d > d2[j][k]) d2[j][k] = d;
}
}
}
}
}
}
int lca(int a, int b, int w)
{
static int distance[N * 2];
int cnt = 0;
if (depth[a] < depth[b]) swap(a, b);
for (int k = 16; k >= 0; k -- )
if (depth[fa[a][k]] >= depth[b])
{
distance[cnt ++ ] = d1[a][k];
distance[cnt ++ ] = d2[a][k];
a = fa[a][k];
}
if (a != b)
{
for (int k = 16; k >= 0; k -- )
if (fa[a][k] != fa[b][k])
{
distance[cnt ++ ] = d1[a][k];
distance[cnt ++ ] = d2[a][k];
distance[cnt ++ ] = d1[b][k];
distance[cnt ++ ] = d2[b][k];
a = fa[a][k], b = fa[b][k];
}
distance[cnt ++ ] = d1[a][0];
distance[cnt ++ ] = d1[b][0];
}
int dist1 = -INF, dist2 = -INF;
for (int i = 0; i < cnt; i ++ )
{
int d = distance[i];
if (d > dist1) dist2 = dist1, dist1 = d;
else if (d != dist1 && d > dist2) dist2 = d;
}
if (w > dist1) return w - dist1;
if (w > dist2) return w - dist2;
return INF;
}
int main()
{
scanf("%d%d", &n, &m); //输入n个点,m条边
for (int i = 0; i < m; i ++ )
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c); // a和b相连,边权是c
edge[i] = {a, b, c};
}
LL sum = kruskal();
build();
bfs();
LL res = 1e18;
for (int i = 0; i < m; i ++ )
if (!edge[i].used)
{
int a = edge[i].a, b = edge[i].b, w = edge[i].w;
res = min(res, sum + lca(a, b, w));
}
printf("%lld\n", res);
return 0;
}
树上差分
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010, M = N * 2;
int n, m;
int h[N], e[M], ne[M], idx;
int depth[N], fa[N][17];
int d[N];
int q[N];
int ans;
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
void bfs()
{
memset(depth, 0x3f, sizeof depth);
depth[0] = 0, depth[1] = 1;
int hh = 0, tt = 0;
q[0] = 1;
while (hh <= tt)
{
int t = q[hh ++ ];
for (int i = h[t]; ~i; i = ne[i])
{
int j = e[i];
if (depth[j] > depth[t] + 1)
{
depth[j] = depth[t] + 1;
q[ ++ tt] = j;
fa[j][0] = t;
for (int k = 1; k <= 16; k ++ )
fa[j][k] = fa[fa[j][k - 1]][k - 1];
}
}
}
}
int lca(int a, int b)
{
if (depth[a] < depth[b]) swap(a, b);
for (int k = 16; k >= 0; k -- )
if (depth[fa[a][k]] >= depth[b])
a = fa[a][k];
if (a == b) return a;
for (int k = 16; k >= 0; k -- )
if (fa[a][k] != fa[b][k])
{
a = fa[a][k];
b = fa[b][k];
}
return fa[a][0];
}
int dfs(int u, int father)
{
int res = d[u];
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if (j != father)
{
int s = dfs(j, u);
if (s == 0) ans += m;
else if (s == 1) ans ++ ;
res += s;
}
}
return res;
}
int main()
{
scanf("%d%d", &n, &m); //n个点,m条边
memset(h, -1, sizeof h);
for (int i = 0; i < n - 1; i ++ )
{
int a, b; // 表示a和b连通
scanf("%d%d", &a, &b);
add(a, b), add(b, a);
}
bfs();
for (int i = 0; i < m; i ++ )
{
int a, b;
scanf("%d%d", &a, &b);
int p = lca(a, b);
d[a] ++, d[b] ++, d[p] -= 2; // 这里是对边做差分
// 如果要对点做差分
//d[a] ++, d[b] ++, d[p] --, d[fa[p][0]] --;
}
dfs(1, -1);
printf("%d\n", ans);
return 0;
}
堆优化Dijstra
能用堆优化就不要用spfa
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
typedef pair<int, int> PII;
const int N = 50010, M = 200010, INF = 0x3f3f3f3f;
int n, m;
int h[N], e[M], w[M], ne[M], idx;
int q[N], dist[6][N];
int source[6];
bool st[N];
int cnt[N]; // 用来统计有几条最短路
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
void dijkstra(int start, int dist[])
{
memset(dist, 0x3f, N * 4);
dist[start] = 0;
memset(st, 0, sizeof st);
priority_queue<PII, vector<PII>, greater<PII>> heap;
heap.push({0, start});
while (heap.size())
{
auto t = heap.top();
heap.pop();
int ver = t.second;
if (st[ver]) continue;
st[ver] = true;
for (int i = h[ver]; ~i; i = ne[i])
{
int j = e[i];
if (dist[j] > dist[ver] + w[i])
{
dist[j] = dist[ver] + w[i];
cnt[j] += cnt[ver]; // 统计有少条最短路
heap.push({dist[j], j});
} else if(dist[j] == dist[ver] + w[i]){
cnt[j] += cnt[ver]; // 统计有多少条最短路,如果不需要就不加这段话
}
}
}
}
int dfs(int u, int start, int distance)
{
if (u > 5) return distance;
int res = INF;
for (int i = 1; i <= 5; i ++ )
if (!st[i])
{
int next = source[i];
st[i] = true;
res = min(res, dfs(u + 1, i, distance + dist[start][next]));
st[i] = false;
}
return res;
}
int main()
{
scanf("%d%d", &n, &m); //n个点,m条边
source[0] = 1;
for (int i = 1; i <= 5; i ++ ) scanf("%d", &source[i]);// 从起点出发,必须要经过的五个位置
memset(h, -1, sizeof h);
while (m -- )
{
int a, b, c; //输入无向边
scanf("%d%d%d", &a, &b, &c);
add(a, b, c), add(b, a, c);
}
for (int i = 0; i < 6; i ++ ) dijkstra(source[i], dist[i]); // 参数是起点和dist数组,dist[i][j]表示从i出发到j的最短距离
memset(st, 0, sizeof st);
printf("%d\n", dfs(1, 0, 0));
return 0;
}
拓扑排序+dijistra
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
#define x first
#define y second
using namespace std;
typedef pair<int, int> PII;
const int N = 25010, M = 150010, INF = 0x3f3f3f3f;
int n, mr, mp, S;
int id[N];
int h[N], e[M], w[M], ne[M], idx;
int dist[N], din[N];
vector<int> block[N];
int bcnt;
bool st[N];
queue<int> q;
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
void dfs(int u, int bid)
{
id[u] = bid, block[bid].push_back(u);
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if (!id[j])
dfs(j, bid);
}
}
void dijkstra(int bid)
{
priority_queue<PII, vector<PII>, greater<PII>> heap;
for (auto u : block[bid])
heap.push({dist[u], u});
while (heap.size())
{
auto t = heap.top();
heap.pop();
int ver = t.y, distance = t.x;
if (st[ver]) continue;
st[ver] = true;
for (int i = h[ver]; ~i; i = ne[i])
{
int j = e[i];
if (id[j] != id[ver] && -- din[id[j]] == 0) q.push(id[j]);
if (dist[j] > dist[ver] + w[i])
{
dist[j] = dist[ver] + w[i];
if (id[j] == id[ver]) heap.push({dist[j], j});
}
}
}
}
void topsort()
{
memset(dist, 0x3f, sizeof dist);
dist[S] = 0;
for (int i = 1; i <= bcnt; i ++ )
if (!din[i])
q.push(i);
while (q.size())
{
int t = q.front();
q.pop();
dijkstra(t);
}
}
int main()
{
cin >> n >> mr >> mp >> S; // n个点,mr个无向边,mp有向边,起点S
memset(h, -1, sizeof h);
while (mr -- )
{
int a, b, c;
cin >> a >> b >> c; //输入无向边
add(a, b, c), add(b, a, c);
}
for (int i = 1; i <= n; i ++ )
if (!id[i])
{
bcnt ++ ;
dfs(i, bcnt);
}
while (mp -- )
{
int a, b, c; // 输入有向边
cin >> a >> b >> c;
din[id[b]] ++ ;
add(a, b, c);
}
topsort();
for (int i = 1; i <= n; i ++ )
if (dist[i] > INF / 2) cout << "NO PATH" << endl; // 因为有负环,所以INF可以更新INF所以当dist[i] > INF/2 的时候就是没有路径了
else cout << dist[i] << endl;
return 0;
}
最小生成树——Prim,Kruskal
Prim 时间复杂度 O(n^2); 稠密图 ;邻接矩阵存储
Kruskal 时间复杂度 O(mlogm); 稀疏图;三元组存储
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 110;
int n;
int w[N][N];
int dist[N];
bool st[N];
int prim()
{
int res = 0;
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
for (int i = 0; i < n; i ++ )
{
int t = -1;
for (int j = 1; j <= n; j ++ )
if (!st[j] && (t == -1 || dist[t] > dist[j]))
t = j;
if(dist[t] == 0x3f3f3f3f) {
return -1; // 如果最小生成树不存在就输出-1
}
res += dist[t];
st[t] = true;
for (int j = 1; j <= n; j ++ ) dist[j] = min(dist[j], w[t][j]);
}
return res;
}
int main()
{
cin >> n; // n*n 的图
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
cin >> w[i][j]; // 邻接矩阵存图
cout << prim() << endl;
return 0;
}
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 110, M = 210;
int n, m;
struct Edge
{
int a, b, w;
bool operator< (const Edge &t)const
{
return w < t.w;
}
}e[M];
int p[N];
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
int main()
{
cin >> n >> m; // n个点,m条边
for (int i = 1; i <= n; i ++ ) p[i] = i;
for (int i = 0; i < m; i ++ )
{
int a, b, w; // 输入三元组
cin >> a >> b >> w;
e[i] = {a, b, w};
}
//执行Kruskal
sort(e, e + m);
int res = 0;
for (int i = 0; i < m; i ++ )
{
int a = find(e[i].a), b = find(e[i].b), w = e[i].w;
if (a != b) p[a] = b; // 如果a, b不在一个集合,就把它放到一个集合里面
else res += w; // 把不要的边相加
}
cout << res << endl;
return 0;
}
SPFA求负环
SPFA 求负环有两种办法:
(1)统计每个点入队的次数,如果某个点入队 n 次,则存在负环
(2)统计每个点最短路包含边数,如果某个点的最短路边数 >= n 也说明存在环
只有有向图才有这样的问题,下面的那个板子也是在有向图上做SPFA
//板子
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 510, M = 5210;
int n, m1, m2;
int h[N], e[M], w[M], ne[M], idx;
int dist[N];
int q[N], cnt[N];
bool st[N];
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, 0, sizeof dist);
memset(cnt, 0, sizeof cnt);
memset(st, 0, sizeof st);
int hh = 0, tt = 0;
for (int i = 1; i <= n; i ++ )
{
q[tt ++ ] = i;
st[i] = true;
}
while (hh != tt)
{
int t = q[hh ++ ];
if (hh == N) hh = 0;
st[t] = false;
for (int i = h[t]; ~i; 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; // 如果这个点可以被更新,这个点的边数就是t这个点的最短路的边数 + 1
if (cnt[j] >= n) return true; // 如果这个点的边数>= n 那么就存在负环,这里面n是点的数量,注意看有的题目是按点建立边的,可能需要把n改成N
if (!st[j])
{
q[tt ++ ] = j;
if (tt == N) tt = 0;
st[j] = true;
}
}
}
}
return false;
}
int main()
{
int T; // 有t次询问
scanf("%d", &T);
while (T -- )
{
scanf("%d%d%d", &n, &m1, &m2); //n个点 m1 无向边 m2 有向边
memset(h, -1, sizeof h);
idx = 0;
for (int i = 0; i < m1; i ++ )
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c); //读入无向图
add(a, b, c), add(b, a, c);
}
for (int i = 0; i < m2; i ++ )
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c); // 读入有向图
add(a, b, -c); //边权是负数
}
if (spfa()) puts("YES");
else puts("NO");
}
return 0;
}
0/1分数规划
二分答案,一般是求环的边权和/点权和最大
即:边权和/点权和>mid
边权和>mid*点权和
边权和-mid*点权和>0
(边权-mid*点权)的和>0
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010, M = 5010;
int n, m;
int wf[N];
int h[N], e[M], wt[M], ne[M], idx;
double dist[N];
int q[N], cnt[N];
bool st[N];
void add(int a, int b, int c)
{
e[idx] = b, wt[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
bool check(double mid)
{
memset(dist, 0, sizeof dist);
memset(st, 0, sizeof st);
memset(cnt, 0, sizeof cnt);
int hh = 0, tt = 0;
for (int i = 1; i <= n; i ++ )
{
q[tt ++ ] = i;
st[i] = true;
}
while (hh != tt)
{
int t = q[hh ++ ];
if (hh == N) hh = 0;
st[t] = false;
for (int i = h[t]; ~i; i = ne[i])
{
int j = e[i];
if (dist[j] < dist[t] + wf[t] - mid * wt[i]) // sfpa 的判断符号要反过来,因为是判断是否有正环,也就是求最长路
{
dist[j] = dist[t] + wf[t] - mid * wt[i];
cnt[j] = cnt[t] + 1;
if (cnt[j] >= n) return true;
if (!st[j])
{
q[tt ++ ] = j;
if (tt == N) tt = 0;
st[j] = true;
}
}
}
}
return false;
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i ++ ) cin >> wf[i]; // 输入点权
memset(h, -1, sizeof h);
for (int j = 0; j < m; j ++ )
{
int a, b, c; // 输入边和边权
cin >> a >> b >> c;
add(a, b, c); // 建图
}
double l = 0, r = 1e6;
while (r - l > 1e-4)
{
double mid = (l + r) / 2;
if (check(mid)) l = mid;
else r = mid;
}
printf("%.2lf\n", l);
return 0;
}
强连通分量
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 10010, M = 50010;
int n, m; // n个点 m条边
int h[N], e[M], ne[M], idx; // 存图
int dfn[N], low[N], timestamp; // 强联通分量
int stk[N], top;
bool in_stk[N];
int id[N], scc_cnt, Size[N]; // id[x] 表示x 点在id[x]这个连通分量里,scc_cnt表示连通分量的数量,size 连通分量的大小
int dout[N]; // 统计出度
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
void tarjan(int u)
{
dfn[u] = low[u] = ++ timestamp;
stk[ ++ top] = u, in_stk[u] = true;
for (int i = h[u]; i != -1; i = ne[i])
{
int j = e[i];
if (!dfn[j])
{
tarjan(j);
low[u] = min(low[u], low[j]);
}
else if (in_stk[j]) low[u] = min(low[u], dfn[j]);
}
if (dfn[u] == low[u])
{
++ scc_cnt;
int y;
do {
y = stk[top -- ];
in_stk[y] = false;
id[y] = scc_cnt;
Size[scc_cnt] ++ ;
} while (y != u);
}
}
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);
}
for (int i = 1; i <= n; i ++ )
if (!dfn[i])
tarjan(i); // tarjan 求强连通分量
// 缩点
for (int i = 1; i <= n; i ++ )
for (int j = h[i]; ~j; j = ne[j])
{
int k = e[j];
int a = id[i], b = id[k];
if (a != b) dout[a] ++ ;
}
// 做拓扑排序,找出度为零的强连通分量
// 强连通分量建出来的图,一定是按照拓扑序的逆序排列的
int zeros = 0, sum = 0;
for (int i = 1; i <= scc_cnt; i ++ )
if (!dout[i])
{
zeros ++ ;
sum += Size[i];
if (zeros > 1)
{
sum = 0;
break;
}
}
printf("%d\n", sum);
return 0;
}