int head[MAXN];//表头 int tot;//边的总数 structEDGE { int to, next, val; }edge[MAXM]; voidadd_edge(int from,int to,int val){ //添加一条边 edge[++tot].to = to; edge[tot].val = val; edge[tot].next = head[from]; head[from] = tot; } //初始化 tot = 0; memset(head, 0, sizeof head);
树与图的遍历
时间复杂度 O(n+m)n 表示点数 ,m 表示边数
深度优先遍历:
1 2 3 4 5 6 7 8
int vis[MAXN];//标记某个点有没有被遍历过 voiddfs(int now){ vis[now] = 1; for(int i = head[now]; i; i = edge[i].next) { int to = edge[i].to; if (!vis[to]) dfs(to); } }
宽度优先遍历:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
int vis[MAXN];//标记某个点有没有被遍历过 voidbfs(int s){ queue<int> q; vis[s] = 1;//标记起点 q.push(s); while (!q.empty()) { int now=q.front(); q.pop(); for (int i = head[now]; i; i = edge[i].next) { int to = edge[i].to; if (!vis[to]) { vis[to] = true;//表示to已经被遍历过 q.push(to); } } } }
int n, ans; constint N = 1e4 + 10; int f[N]; structnode { int v;int w; }; vector<node> ve[N];
intdfs(int u, int pre){ int dis = 0; int d1 = 0, d2 = 0; for (node i : ve[u]) { if (i.v == pre) continue; int d = dfs(i.v, u) + i.w; dis = max(dis, d); if (d >= d1) { d2 = d1, d1 = d; } elseif (d > d2) d2 = d; }
ans = max(ans, d1 + d2); return dis; }
intmain(void){ ios::sync_with_stdio(false); cin.tie(0);cout.tie(0); cin >> n; int u, v, w; for (int i = 1; i < n; ++i) { cin >> u >> v >> w; ve[u].push_back(node{v, w}), ve[v].push_back(node{u, w}); }
int n; constint N = 1e4 + 10; int dis[N]; structnode { int v;int w; }; vector<node> ve[N];
voiddfs(int u, int pre){ for (node i : ve[u]) { if (i.v == pre) continue; dis[i.v] = dis[u] + i.w; dfs(i.v, u); } }
intmain(void){ ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); cin >> n; int u, v, w, t, res; for (int i = 1; i < n; ++i) { cin >> u >> v >> w; ve[u].push_back(node{v, w}), ve[v].push_back(node{u, w}); } dis[1] = 0; dfs(1, 0); t = -1, res = -1; for (int i = 1; i <= n; ++i) { if (dis[i] > res) { res = dis[i], t = i; } } dis[t] = 0; dfs(t, 0); t = -1, res = -1; for (int i = 1; i <= n; ++i) { if (dis[i] > res) { res = dis[i], t = i; } } printf("%d\n", res);
#include<bits/stdc++.h> usingnamespace std; #define ll long long
int n; constint N = 1e4 + 10; structdist { int d1, d2, inx; }; dist down[N]; structnode { int v, w; }; vector<node> ve[N]; int up[N], f[N];
intdfs(int cur, int pre){ int d1 = 0, d2 = 0, inx = 0; for (node i : ve[cur]) { if (i.v == pre) continue; int d = dfs(i.v, cur) + i.w; if (d > d1) { inx = i.v, d2 = d1, d1 = d; } elseif (d > d2) { d2 = d; } } down[cur] = dist{d1, d2, inx}; return d1; }
voiddp(int cur, int pre, int d){ int inx = down[pre].inx, d1 = down[pre].d1, d2 = down[pre].d2; up[cur] = up[pre] + d; if (inx == cur) { up[cur] = max(up[cur], d2 + d); } else { up[cur] = max(up[cur], d1 + d); } f[cur] = max(up[cur], down[cur].d1); for (node i : ve[cur]) { if (i.v == pre) continue; dp(i.v, cur, i.w); } }
intmain(void){ int u, v, w; ios::sync_with_stdio(false); cin.tie(0);cout.tie(0); cin >> n; for (int i = 1; i < n; ++i) { cin >> u >> v >> w; ve[u].push_back(node{v, w}), ve[v].push_back(node{u, w}); }
dfs(1, 0); dp(1, 0, 0);
int res = 1e9 + 10; for (int i = 1; i <= n; ++i) { res = min(res, f[i]); } cout << res << endl;
ll n, m; ll dis[505][505]; voidinit(){ for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++) if (i == j) dis[i][j] = 0; else dis[i][j] = INF; }
voidsolve(){ n = read(), m = read(); init(); for (int i = 1; i <= m; i++) { int u = read(), v = read(); dis[u][v] = 1; dis[v][u] = 1; } ll ans = 0; for (int k = 1; k <= n; k++) for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++) dis[i][j] = min(dis[i][k] + dis[k][j], dis[i][j]); for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++) ans = max(ans, dis[i][j]); printf("%lld\n", ans); }
//初始化 #define INF 0x3f3f3f3f int ans = INF; memset(dis, 0x3f, sizeof(dis)); memset(a, 0x3f, sizeof(a));
//输入图中两点边权a[i][j]
for (int k = 1; k <= n; k++) { for (int i = 1; i < k; i++) for (int j = i + 1; j < k; j++) ans = min(ans, dis[i][j] + a[i][k] + a[k][j]); //答案为i到j的最短路径权值加上j到k的边权和i到k的边权 for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++) dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]); //更新i到j的最短路径 }
#include<bits/stdc++.h> usingnamespace std; #define ll long long #define MAXN 200005 int ans = 0x3f3f3f3f;//最小环的个数 int f[MAXN]; ll n; int cnt; intfindx(int x, int &cnt){ cnt++; if (x == f[x]) return x; elsereturnfindx(f[x], cnt); }
voidsolve(){ cin >> n; for (int i = 1; i <= n; i++) f[i] = i; for (int i = 1; i <= n; i++) { int temp; cnt = 0; cin >> temp; int goal = findx(temp, cnt); if (goal == i) { ans = min(ans, cnt); } else f[i] = temp; } cout << ans << endl; }
intdij(int s){ for (int i = 1; i <= n; i++) dis[i] = INF, vis[i] = 0; dis[s] = 0; priority_queue<pii, vector<pii>, greater<pii>> q; q.push({dis[s], s}); while (q.size()) { int now = q.top().second; q.pop(); if (vis[now]) continue; vis[now] = 1; for (int i = head[now]; i; i = edge[i].next) { int to = edge[i].to; ll val = edge[i].val; if (to == s && dis[now] + val <= c) return1; if (dis[to] > dis[now] + val) { dis[to] = dis[now] + val; q.push({dis[to], to}); } } } return0; }
for (int i = 1; i <= n; i++) { int tmp = dij(i); if (tmp == 1) { ans++; break; } }
正常求最小环的话应该遍历起始点,然后每次找到一个环就和当前的就好了
拓扑排序
时间复杂度 O(n+m) , n 表示点数,m 表示边数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
int indgr[MAXN];//记录有向图的入度数 voidtopu(){ queue<int> q; for (int i = 1; i <= n; i++) if (!indgr[i])//将入度数为0的点推入队列 q.push(i); while (!q.empty()) { int now = q.front(); q.pop(); for (int i = head[now]; i; i = edge[i].next) { int to = edge[i].to; //进行相关操作 if (--indgr[to] == 0) q.push(to); } } }
ll n, m; structnode { ll val; int u, v; }a[MAXM]; ll dis[MAXN];
voidsolve(){ n = read(), m = read(); for (int i = 1; i <= m; i++) { int u = read(), v = read(); ll w = read(); a[i] = {-w, u, v}; } memset(dis, 0x3f, sizeof dis); dis[1] = 0; for (int i = 1; i <= n; i++) { for (int i = 1; i <= m; i++) { ll val = a[i].val; int u = a[i].u; int v = a[i].v; if (dis[v] > dis[u] + val) { dis[v] = dis[u] + val; } } } ll ans = dis[n]; for (int i = 1; i <= m; i++) { ll val = a[i].val; int u = a[i].u; int v = a[i].v; if (dis[v] > dis[u] + val) { dis[v] = dis[u] + val; } } if (dis[n] != ans) //n号节点的负环中 puts("inf"); else printf("%lld\n", -ans); }
// 如果第n次迭代仍然会松弛三角不等式,就说明存在一条长度是n+1的最短路径,由抽屉原理,路径中至少存在两个相同的点,说明图中存在负权回路。 for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { int a = edges[j].a, b = edges[j].b, w = edges[j].w; if (dist[b] > dist[a] + w) dist[b] = dist[a] + w; } }
if (dist[n] > 0x3f3f3f3f / 2) return-1; return dist[n]; }
#define INF 0x3f3f3f3f int n;//总点数 int dis[MAXN];//存储每个点到s的最短距离 int vis[MAXN];//存储每个点是否在队列中 int cnt[MAXN];//存储1到x的最短路中经过的点数 boolspfa(int s){ //如果存在负环,返回true;否则返回false queue<int> q; //不需要初始化dis数组 for (int i = 1; i <= n; i++) { q.push(i); vis[i] = 1; } while (!q.empty()) { int now = q.front(); q.pop(); vis[now] = 0; for (int i = head[now]; i; i = edge[i].next) { int to = edge[i].to; int val = edge[i].val; if (dis[to] > dis[now] + val) { dis[to] = dis[now] + val; cnt[to] = cnt[now] + 1; if (cnt[to] >= n) returntrue;//一条路径上包含至少n个点说明存在环 if (!vis[to]) { q.push(to); vis[to] = 1; } } } } returnfalse; }
SPFA判正环
同样,SPFA判正环只需将松弛操作的符号反一下即可。
差分约束系统
如果一个系统由 n 个变量和 m 个不等式组成,并且这 m 个不等式均为 xi−xj≤a[k] 的形式,这样的系统成为 差分约束
#define INF 0x3f3f3f3f int n, m;//n表示点数,m表示边数 int f[MAXN];//并查集的父节点数组 structEDGE { int a, b, w; booloperator< (const EDGE &W) const { return w < W.W; } }edge[MAXM];
intkruskal(){ sort(edge + 1, edge + 1 + m); for (int i = 1; i <= n; i++) f[i] = i;//初始化并查集 int res = 0, cnt = 0; for (int i = 1; i <= m; i++) { int x = edge[i].a, y = edge[i].b, w = edge[i].w; x = findx(x), y = findx(y); if (x != y) { f[y] = x; cnt++; res += w; } } if (cnt < n - 1) return INF; return res; }
LCA相关
倍增求LCA
预处理时间复杂度 O(nlogn) ,单次查询时间复杂度 O(logn)
f[i][j] 表示从 i 向上走 2j 次步的节点的编号 0≤j≤logn
depth[i] 表示节点 i 的深度,默认根节点的深度为 1 。节点 i 的深度=与根节点的距离+1。
int n, m, rt;//点数,边数,根 int depth[MAXN];//点的深度 int fa[MAXN][25];
voidbuild(int rt){ //预处理fa数组 memset(depth, 0x3f, sizeof(depth)); depth[0] = 0;//哨兵 depth[rt] = 1; queue<int> q; q.push(rt); while (!q.empty()) { int now = q.front(); q.pop(); for (int i = head[now]; i; i = edge[i].next) { int to = edge[i].to; if (depth[to] > depth[now] + 1){ //to还没搜索过 depth[to] = depth[now] + 1; q.push(to); fa[to][0] = now;//to的上一层为now for (int k = 1; k <= 24; k++) { fa[to][k] = fa[fa[to][k-1]][k-1];//向上更新 } } } } }
intlca(int x, int y){ if (depth[x] < depth[y]) swap(x, y); for (int k = 24; k >= 0; k--) { //跳到相同的深度 if (depth[fa[x][k]] >= depth[y]) x = fa[x][k]; } if (x == y) return x; for (int k = 24; k >= 0; k--) { //一起往上跳 if (fa[x][k] != fa[y][k]) { x = fa[x][k]; y = fa[y][k]; } } return fa[x][0]; }
voidsolve(){ build(rt); while (m--) { int x, y; cin >> x >> y; cout << lca(x, y) <<endl; } }
for (int i = 1; i < n; i++) { //连边 int u, v, w; cin >> u >> v >> w; add_edge(u, v, w); add_edge(v, u, w); } // vector <pii> mp[MAXN] first表示另一个点的编号,second表示方案的编号 for (int i = 1; i <= m; i++) { //记录查询方案 int u, v; cin >> u >> v; if (u != v) { mp[u].push_back({v, i}); mp[v].push_back({u, i}); } }
然后对任意点跑一个 tarjan
1
tarjan(1);
tarjan 算法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
voidtarjan(int now){ vis[now] = 1; //表示正在搜索这个点 for (int i = head[now]; i; i = edge[i].next) { int to = edge[i].to; if (!vis[to]) tarjan(to), f[to] = now; //合并到根节点 } for (auto x : mp[now]) { //遍历和该点相关的所有查询方案 int to = x.first; int tim = x.second; if (vis[to] == 2) { //该点已完成回溯,LCA为f[to] int fa = findx(to); ans[tim] = dis[now] + dis[to] - 2 * dis[fa]; } } vis[now] = 2; //该点已完成回溯 }
//以最长链方案统计为例 for (int now = scc; now; now--) { if (!dp[now]) dp[now] = sccc[now], cnt[now] = 1; for (int i = head[now]; i; i = edge[i].next) { int to = edge[i].to; int val = sccc[to]; if (dp[to] == dp[now] + val) { cnt[to] = (cnt[to] + cnt[now]) % mod; } elseif (dp[to] < dp[now] + val) { dp[to] = dp[now] + val; cnt[to] = cnt[now]; } } }
缩点
把环看成一个点进行后面的算法。一般缩点后的点权是环路中所有点的点权和。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
int n; int key[MAXN]; int h[MAXN]; //原图 int head[MAXN]; //新图 voidsolve(){ for (int now = 1; now <= n; now++) { for (int i = h[now]; i; i = edge[i].next) { int to = edge[i].to; if (key[now] == key[to]) continue; //如果两端属于一个强连通分量放弃连边 add_edge(head, key[now], key[to]); //将两个点对应的强连通分量编号连边 //单点也属于强连通分量 } } }
这是遍历点,也可以遍历边。如果一条边的两个端点不是属于两个强连通分量,就在新图上连一条边。
缩点哈希判重
1 2 3 4 5 6 7 8 9 10 11 12 13 14
set<ll> s; for (int now = 1; now <= n; now++) { for (int i = hh[now]; i; i = edge[i].next) { int to = edge[i].to; if (key[now] == key[to]) continue; ll hash = key[now] * 1000000ll + key[to]; if (s.count(hash)) continue; //判重边 add_edge(head, key[now], key[to]); indgr[key[to]]++; s.insert(hash); } }
边双连通分量
对于一张连通的无向图,如果删除图中的某条边之后原先的图不能成为连通图,那么这条边就被称为 桥
极大的不包含桥的连通块,被称为 边双连通分量(e-dcc)
对于边双连通分量,不管删除哪条边,图依旧是连通的。
如何判断桥?
判断 y 能否走到 x 的祖先节点!!!如果不能,则为桥。
dfn(x)<low(y)
如何找到所有边双连通分量?
利用栈维护,如果dfn(x)==low(x) ,表示 x 无法返回他的祖先节点,说明 x 的父节点与 x 的边即为桥,还在栈当中的节点即为边双连通分量。
对于一棵树,所有边都是桥。
写的时候因为是无向边,我们要新加一个参数 pre (pre 是边不是点)防止走回头路,因为如果走回头路的话必然找不到桥了。
int n, m; //点数,边数 int num, dfn[MAXN], low[MAXN]; //时间戳相关 int dcc, key[MAXN], dccc[MAXN]; //边双连通分量相关 stack<int> st; vis[MAXN]; //栈 int bridge[MAXM]; //判断某条边是不是桥,1表示是
int n, m; //点数,边数 int color[MAXN]; //表示每个点的颜色,-1表示还未染色 booldfs(int now, int c){ // x表示点编号,c表示颜色 color[now] = c; for (int i = head[now]; i; i = edge[i].next) { int to = edge[i].to; if (color[to] == -1) { if (!dfs(to, !c)) //搜完后出现了矛盾 returnfalse; } elseif (color[to] == c) //之前的结果和现在的结果产生矛盾 returnfalse; } returntrue; }
voidsolve(){ for (int i = 1; i <= n; i++) { if (color[i] == -1) { if (!dfs(i, 0)) { //染色时出现了矛盾 cout << "No" << endl; return; } } } cout << "Yes" << endl; }
int n, nn, m; // n表示第一个点集中的点数,nn表示第二个点集中的点数 int vis[MAXN]; // vis表示第二个集合中的某个点是否已经被遍历过 int match[MAXN]; // match表示第二个集合中的某个点当前匹配的第一个集合中的点是哪个 int ans = 0; //最大匹配数
boolfind(int now){ for (int i = head[now]; i; i = edge[i].next) { int to = edge[i].to; if (!vis[to]) { //如果这个now可能的对象还未搜索过 vis[to] = 1; //标记一下,防止重复搜索 if (match[to] == 0 || find(match[to])) { //如果她当前还没对象或者可以绿她的对象 match[to] = now; returntrue; } } } returnfalse; }
voidsolve(){ for (int i = 1; i <= n; i++) { //跑N趟DFS memset(vis, 0, sizeof(vis)); if (find(i)) ans++; } cout << ans << endl; }
int n, nn, m; // n表示第一个点集中的点数,nn表示第二个点集中的点数 int vis[MAXN]; // vis表示第二个集合中的某个点是否已经被遍历过 int match[MAXN]; // match表示第二个集合中的某个点当前匹配的第一个集合中的点是哪个 int ans = 0; //最大匹配数
boolfind(int now){ for (int i = head[now]; i; i = edge[i].next) { int to = edge[i].to; if (!vis[to]) { //如果这个now可能的对象还未搜索过 vis[to] = 1; //标记一下,防止重复搜索 if (match[to] == 0 || find(match[to])) { //如果她当前还没对象或者可以绿她的对象 match[to] = now; returntrue; } } } returnfalse; }
voidsolve(){ for (int i = 1; i <= n; i++) { //跑N趟DFS memset(vis, 0, sizeof(vis)); if (find(i)) ans++; } cout << ans << endl; }
int type; //有向图 or 无向图 int n, m; //点,边的数量 int ans[MAXM], cnt; //记录欧拉回路用,必须是边的数量 int vis[MAXM]; //标记某条边有没有走过 int indgr[MAXN], oudgr[MAXN]; //点的入度和出度来表示度数
voiddfs(int now){ //判重删边,dfs,记录答案 for (int &i = head[now]; i;) { //改变引用后就能直接删边了 if (vis[i]) { //这条边用过了 i = edge[i].next; //删除这条边 continue; } vis[i] = 1; if (type == 1) //如果是无向边,反向边也要标记删除 vis[get(i)] = 1; int id; //表示边的编号 if (type == 1) { //无向图 id = (i + 1) / 2; if (i % 2 == 0) //本题特有的脑瘫条件,不用管 id = -id; } else//有向图 id = i; int j = edge[i].to; i = edge[i].next; dfs(j); ans[++cnt] = id; } if (type == 2) { //判断度数合法性 for (int i = 1; i <= n; i++) { if (indgr[i] != oudgr[i]) { cout << "NO" << endl; return; } } } else { for (int i = 1; i <= n; i++) { if ((indgr[i] + oudgr[i]) & 1) { cout << "NO" << endl; return; } } }
for (int i = 1; i <= n; i++) { //找一个非孤立点去遍历整张图 if (head[i] != 0) { dfs(i); break; //一定要break掉,因为有非连通的情况存在 } } if (cnt < m) { //判断图的连通性 cout << "NO" << endl; } cout << "YES" << endl; for (int i = cnt; i > 0; i--) //欧拉路要倒着输出 cout << ans[i] << " "; cout << endl; }
#include<bits/stdc++.h> usingnamespace std; #define ll long long #define MAXN 505 #define MAXM 2105 int mp[MAXN][MAXN]; int d[MAXN]; //存储度数 int n, m; int ans[MAXM], cnt;
voiddfs(int now){ for (int i = 1; i <= 500; i++) { if (mp[now][i]) { mp[now][i]--, mp[i][now]--; //删边 dfs(i); //爆搜 } } ans[++cnt] = now; //记录答案 }
voidsolve(){ cin >> m; for (int i = 1; i <= m; i++) { int u, v; cin >> u >> v; mp[u][v]++, mp[v][u]++; d[u]++, d[v]++; } int start = 0; while (!d[start]) start++; //特判欧拉回路的情况 for (int i = 1; i <= 500; i++) { if (d[i] & 1) { //如果是一条欧拉回路前面必须做过处理,否则出不来 start = i; break; } } dfs(start); for (int i = cnt; i; i--) //欧拉路要倒着输出 cout << ans[i] << endl; }