图论:Tarjan

前置概念

时间戳:搜索时第几个搜索到这个点。如搜索顺序是1->2->3->6,则6的时间戳为4

对于无向图

连通分量:对于图G来的一个子图中,任意两个点都可以彼此到达,这个子图就被称为图G的连通分量(一个点就是最小的连通分量)

最大连通分量:对于图G的一个子图,这个子图为图G的连通分量,且是图G所有连通分量中包含节点数最多的那个,即为G的最大联通分量

算法流程

推荐看看这篇

简单应用

缩点

有向图强连通分量:在有向图G中,如果两个顶点vi,vj间(vi>vj)有一条从vi到vj的有向路径,同时还有一条从vj到vi的有向路径,则称两个顶点强连通(strongly connected)。孤立的一个点也是一个强连通分量。

如果有向图G的每两个顶点都强连通,称G是一个强连通图。有向图的极大强连通子图,称为强连通分量(SCC, Strongly Connected Components)。把每个强联通分量看成一个集合,把每个集合看成一个点,那么所有SCC就形成了一个DAG(有向无环图),就是著名的缩点。

Problem

根据题目意思,我们只需要找出一条点权最大的路径就行了,不限制点的个数。那么考虑对于一个环上的点被选择了,一整条环是不是应该都被选择,这一定很优,能选干嘛不选。很关键的是题目还允许我们重复经过某条边或者某个点,我们就不需要考虑其他了。因此整个环实际上可以看成一个点(选了其中一个点就应该选其他的点)

在处理了环后,我们就重新建立一张图,以每个环为节点(孤立一个点也算也算环的,其实也就是强联通分量了)。在这张图中我们要dp,显然对于任意边<u,v>,dp[v] = max(dp[v], dp[u] + new_weight[v])

dp的时候拓扑排一下序,这也是DAGdp的常见trick了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#include <bits/stdc++.h>
using namespace std;
const int N = 1e4+10, M = 1e5+10;
struct node {
int u, v, nxt;
} e[M];
int h[N], tot = 0;
int dfn[N], low[N], tag = 0, top = 0, num = 0;
int n, m, w[N], ww[N], f[N], id[N], s[N], in[N];
void tarjan(int u) {

dfn[u] = low[u] = ++tag;
s[++top] = u;
for(int i = h[u], v; i; i = e[i].nxt) {
v = e[i].v;
if(!dfn[v]) {
tarjan(v);
low[u] = min(low[u], low[v]);
} else if(!id[v]) low[u] = min(low[u], dfn[v]);
}

if(dfn[u] == low[u]) {
++num;
while(s[top] != u) {
id[s[top]] = num; ww[num] += w[s[top]]; top--;
}
id[s[top]] = num; ww[num] += w[s[top]]; top--;
}

}

int main() {

scanf("%d%d", &n, &m);
for(int i=1; i<=n; ++i) scanf("%d", w+i);

for(int i=1; i<=m; ++i) {
int u, v; scanf("%d%d", &u, &v);
if(u == v) continue; e[++tot] = {u, v, h[u]}; h[u] = tot;
}
for(int i=1; i<=n; ++i) {
if(!dfn[i]) tarjan(i);
}

memset(h, 0, sizeof h);
for(int i=0, t=0; i<tot; ++i) {
int u = id[e[i].u], v = id[e[i].v];
if(u == v); else {
e[++t] = {u, v, h[u]}; h[u] = t;
++in[v];
}
}

queue<int> q;
for(int i=1; i<=num; ++i) {
if(!in[i]) q.push(i);
}
int ans = 0;
for(int i=1; i<=num; ++i) f[i] = ww[i], ans = max(ans, f[i]);
while(!q.empty()) {
int u = q.front(); q.pop();
for(int i = h[u]; i; i = e[i].nxt) {
int v = e[i].v;
f[v] = max(f[v], f[u]+ww[v]);
ans = max(ans, f[v]);
--in[v];
if(!in[v]) q.push(v);
}
}
printf("%d\n", ans);

return 0;
}

找割点

割点:去掉无向联通图的某个点后,此图不连通,该点为割点。

Problem

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include <stdio.h>
#include <iostream>
using std::min;

const int N = 22000, M = 110000;

struct node {
int u, v, next;
} e[M << 1];
int h[N], tot;

int dfn[N], low[N], tag;
int res[N], ans, n, m, fa[N];

void tarjan(int u, int fa) {

dfn[u] = low[u] = ++tag;

int sum = 0;
for(int i = h[u], v; i; i = e[i].next) {
v = e[i].v;
if(v == fa) continue;

if(dfn[v]) low[u] = min(low[u], dfn[v]);
else {
++sum;
tarjan(v, u);
low[u] = min(low[u], low[v]);
if(fa == -1) continue;
if(low[v] >= dfn[u]) res[u] = true;
}
}

if(fa == -1 and sum > 1) res[u] = true;

}
int main() {

//freopen("p3388_4.in", "r", stdin);

scanf("%d%d", &n, &m);

for(int i=1, x, y; i<=m; ++i) {
scanf("%d%d", &x, &y);
e[++tot] = (node) {x, y, h[x]}; h[x] = tot;
e[++tot] = (node) {y, x, h[y]}; h[y] = tot;
}

for(int i=1; i<=n; ++i)
if(dfn[i] == 0) tarjan(i, -1);

for(int i=1; i<=n; ++i) if(res[i]) ++ans;
printf("%d\n", ans);
for(int i=1; i<=n; ++i) if(res[i]) printf("%d ", i);

return 0;
}
发布于

2020-11-05

更新于

2022-08-02

许可协议

评论