浅谈图论算法------图的连通性
一, 图的连通性(Topsort)
Topsort是针对DAG(有向无环图),检查其连通性的图论算法。
算法过程:统计所有节点的入度,如果当前节点的入度为0,则将当前节点入队,对于每个节点枚举他的所有出边,出点入度减减,重复此过程。
板子题:Topsort(模板)
cpp
#include<bits/stdc++.h>
using namespace std;
const int N = 1010;
int e[N], ne[N], h[N], idx = 0;
int n, din[N];
inline void add (int a, int b) {
e[idx] = b;
ne[idx] = h[a];
h[a] = idx ++;
}
inline void Topsort() {
queue<int> q;
for (int i = 1; i <= n; i ++) {
if (!din[i]) {
cout << i << " ";
q.push(i);
}
}
while (q.size()) {
int t = q.front(); q.pop();
for (int i = h[t]; ~i; i = ne[i]) {
int j = e[i];
din[j] --;
if (!din[j]) {
cout << j << " ";
q.push(j);
}
}
}
}
int main() {
cin >> n;
memset(h, -1, sizeof h);
for (int i = 1; i <= n; i ++) {
int x;
while (cin >> x && x) {
add (i, x); din[x] ++;
}
}
Topsort();
}
练习题:摄像头
提示:注意摄像头的位置并非全为n
cpp
#include<bits/stdc++.h>
using namespace std;
const int N = 10010;
int e[N], ne[N], h[N], idx = 0;
int n, din[N], ans = 0, x[N];
bool st[N];
inline void add (int a, int b) {
e[idx] = b;
ne[idx] = h[a];
h[a] = idx ++;
}
inline void Topsort() {
queue<int> q;
for (int i = 1; i <= n; i ++) {
if (!din[x[i]]) q.push(i);
}
while (q.size()) {
int t = q.front(); q.pop(); ans ++;
for (int i = h[t]; ~i; i = ne[i]) {
int j = e[i]; din[j] --;
if (!din[j] && st[j]) {
q.push(j);
}
}
}
}
int main() {
cin >> n;
memset(h, -1, sizeof h);
for (int i = 1; i <= n; i ++) {
int m; cin >> x[i] >> m; st[x[i]] = 1;
for (int j = 1; j <= m; j ++) {
int y; cin >> y;
add (x[i], y); din[y] ++;
}
}
Topsort();
if (ans == n) puts("YES");
else cout << n - ans << endl;
}
练习题:游览
cpp
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
const int Mod = 1e4;
int n, m, s, t, t0;
int e[N], ne[N], w[N], h[N], idx = 0;
int cnt[N], dist[N], din[N];
inline void add (int a, int b, int c) {
e[idx] = b;
ne[idx] = h[a];
w[idx] = c;
h[a] = idx ++;
}
inline void Topsort() {
queue<int> q;
for (int i = 1; i <= n; i ++) {
if (!din[i]) q.push(i);
}
while (q.size()) {
int t = q.front(); q.pop();
for (int i = h[t]; ~i; i = ne[i]) {
int j = e[i]; din[j] --;
cnt[j] = (cnt[j] + cnt[t]) % Mod;
dist[j] = ((dist[j] + dist[t]) % Mod + w[i] * cnt[t] % Mod) % Mod;
if (!din[j]) q.push(j);
}
}
}
int main() {
memset(h, -1, sizeof h);
cin >> n >> m >> s >> t >> t0;
for (int i = 1; i <= m; i ++) {
int a, b, c; cin >> a >> b >> c;
add (a, b, c); din[b] ++;
}
cnt[s] = 1;
Topsort();
cout << (dist[t] + t0 * (cnt[t] - 1) % Mod) % Mod << endl;
}
二,图的连通性(Tarjan)
强连通分量:即对于图中的某一部分节点 u 和 v 可以相互到达则称它们同属于一个连通分量
引入一些Tarjan算法会用到的定义
dfn[] 时间戳:表示当前节点在dfs搜索树上的位置
low[]:当前节点最小子树的根节点,初始值为当前节点的dfn
(1) Tarjan求强连通分量
板子题:求强连通分量(模板)
cpp
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
const int M = 2 * N;
int n, m, dfn[M], low[M];
int e[M], ne[M], h[M], idx = 0;
int mark = 0, col[M], sum = 0;
bool vis[M];
stack<int> st;
vector<int> vec[N];
inline void add (int a, int b) {
e[idx] = b;
ne[idx] = h[a];
h[a] = idx ++;
}
inline void Tarjan(int u) {
dfn[u] = low[u] = ++ mark;
vis[u] = 1, st.push(u);
for (int i = h[u]; ~i; i = ne[i]) {
int v = e[i];
if (!dfn[v]) {
Tarjan(v);
low[u] = min (low[u], low[v]);
} else {
if (vis[v]) {
low[u] = min (low[u], low[v]);
}
}
}
if (dfn[u] == low[u]) {
col[u] = ++ sum;
vis[u] = 0;
while (st.top() != u) {
col[st.top()] = sum;
vis[st.top()] = 0;
st.pop();
}
st.pop();
}
}
int main() {
cin >> n >> m;
memset(h, -1, sizeof h);
for (int i = 1; i <= m; i ++) {
int a, b; cin >> a >> b;
add (a, b);
}
for (int i = 1; i <= n; i ++) {
if (!dfn[i]) Tarjan(i);
}
for (int i = 1; i <= n; i ++) {
vec[col[i]].push_back(i);
}
memset(vis, 0, sizeof vis);
cout << sum << endl;
for (int i = 1; i <= n; i ++) {
int color = col[i];
if (!vis[color]) {
vis[color] = 1;
sort (vec[color].begin(), vec[color].end());
for (int j = 0; j < vec[color].size(); j ++) cout << vec[color][j] << " ";
cout << endl;
}
}
}
缩点
由Tarjan求强连通分量我们可以进一步思考这个问题,我们把每个强连通分量看作一个点,这个点的权值记录为在强连通分量中每一个点的权值之和。
板子题:缩点(模板)
cpp
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
const int M = 2 * N;
int e[M], ne[M], h[M], idx = 0, idxx = 0;
int ee[M], nee[M], hh[M], a[N], dist[M];
int n, m, dfn[M], low[M], val[M];
int col[M], mark = 0, cnt = 0, din[M];
bool vis[M];
stack<int> st;
inline void add (int a, int b) {
e[idx] = b;
ne[idx] = h[a];
h[a] = idx ++;
}
inline void add_S(int a, int b) {
ee[idxx] = b;
nee[idxx] = hh[a];
hh[a] = idxx ++;
}
inline void Tarjan(int u) {
dfn[u] = low[u] = ++ mark;
vis[u] = 1, st.push(u);
for (int i = h[u]; ~i; i = ne[i]) {
int v = e[i];
if (!dfn[v]) {
Tarjan(v);
low[u] = min (low[u], low[v]);
} else if (vis[v]) low[u] = min (low[u], low[v]); // 这里用low[v]更新和用dfn[v]更新都是可以的
}
if (dfn[u] == low[u]) {
col[u] = ++ cnt;
vis[u] = 0; val[cnt] = a[u]; // 如果这里不用do...while的话记得要在初始时更新val[cnt]
while (st.top() != u) {
col[st.top()] = cnt;
vis[st.top()] = 0;
val[cnt] += a[st.top()];
st.pop();
}
st.pop();
}
}
inline int Topsort() { // 在进行Topsort的时候记得要用新建的图,也就是缩点后的图
queue<int> q;
for (int i = 1; i <= cnt; i ++) {
dist[i] = val[i];
if (!din[i]) q.push(i);
}
while (q.size()) {
int t = q.front(); q.pop();
for (int i = hh[t]; ~i; i = nee[i]) {
int v = ee[i]; din[v] --;
if (dist[v] < dist[t] + val[v]) {
dist[v] = dist[t] + val[v];
}
if (!din[v]) q.push(v);
}
}
int ans = 0;
for (int i = 1; i <= cnt; i ++) ans = max (ans, dist[i]);
return ans;
}
int main() {
memset(h, -1, sizeof h);
memset(hh, -1, sizeof hh);
cin >> n >> m;
for (int i = 1; i <= n; i ++) cin >> a[i];
for (int i = 1; i <= m; i ++) {
int a, b; cin >> a >> b;
add (a, b);
}
for (int i = 1; i <= n; i ++) {
if (!dfn[i]) Tarjan(i);
}
for (int i = 1; i <= n; i ++) {
for (int j = h[i]; ~j; j = ne[j]) {
int v = e[j];
if (col[i] != col[v]) {
add_S (col[i], col[v]); // 注意建边要建col[u]与col[v]
din[col[v]] ++;
}
}
}
cout << Topsort() << endl;
}
例题:搭配购买
cpp
#include<bits/stdc++.h>
using namespace std;
const int N = 1e4 + 10;
const int M = 2 * N;
int n, m, k, v[M], w[M];
int e[M], ne[M], h[M], idx = 0;
int dfn[M], low[M], mark = 0, dp[M];
int siz[M], val[M], cnt = 0, col[M];
bool vis[M];
stack<int> st;
inline void add (int a, int b) {
e[idx] = b;
ne[idx] = h[a];
h[a] = idx ++;
}
inline void Tarjan(int u) {
dfn[u] = low[u] = ++ mark;
st.push(u); vis[u] = 1;
for (int i = h[u]; ~i; i = ne[i]) {
int p = e[i];
if (!dfn[p]) {
Tarjan(p);
low[u] = min (low[u], low[p]);
} else if (vis[p]) {
low[u] = min (low[u], low[p]);
}
}
if (dfn[u] == low[u]) {
col[u] = ++ cnt;
siz[cnt] += v[u];
val[cnt] += w[u];
vis[u] = 0;
while (st.top() != u) {
vis[st.top()] = 0;
col[st.top()] = cnt;
siz[cnt] += v[st.top()];
val[cnt] += w[st.top()];
st.pop();
}
st.pop();
}
}
int main() {
cin >> n >> m >> k;
for (int i = 1; i <= n; i ++) cin >> v[i] >> w[i];
memset(h, -1, sizeof h);
for (int i = 1; i <= m; i ++) {
int a, b; cin >> a >> b;
add (a, b); add (b, a);
}
for (int i = 1; i <= n; i ++) {
if (!dfn[i]) Tarjan(i);
}
memset(dp, -0x3f, sizeof dp);
dp[0] = 0;
for (int i = 1; i <= cnt; i ++) {
for (int j = k; j >= siz[i]; j --) {
dp[j] = max (dp[j], dp[j - siz[i]] + val[i]);
}
}
int ans = 0;
for (int i = 1; i <= k; i ++) ans = max (ans, dp[i]);
cout << ans << endl;
}
关于Tarjan的一些不太重要的知识点
割点: 在无向图中如果将一点及其连边删去使得原图不连通则称这个点为割点
桥:类似于割点的定义,若在无向图中将一边删去原图不连通则将该边成为桥