目录
1.思路:(1)意思就是前辈放在前面 ;
(2)先找到入度为0 的点,这样就没有是它前辈 的点,它就可以放在最前面 ;
(3)放进去后,它的后辈入度减去1 ,继续找为0的重复上述步骤 即可;
(4)如果放进去的总长度小于点的总数 ,则没有合法 的拓扑序,因为这就说明至少某一次找不到为0的 ,存在互相依赖关系,无法排先后。
2.代码如下:
cpp
#include <bits/stdc++.h>
#define int long long
using namespace std;
int N, a, cnt = 0, into[110], ans[110];
vector<int> g[110];
void topu() {
queue<int> q;
for (int i = 1; i <= N; i++) {
if (!into[i])
q.push(i);
}
while (!q.empty()) {
int u = q.front();
q.pop();
ans[++cnt] = u;
for (int v : g[u]) {
into[v]--;
if (!into[v])
q.push(v);
}
}
}
signed main() {
scanf("%lld", &N);
for (int i = 1; i <= N; i++) {
for (int j = 1; ; j++) {
scanf("%lld", &a);
if (!a)
break;
g[i].push_back(a);
into[a]++;
}
}
topu();
//if (cnt < N)
// printf("No");
for (int i = 1; i <= N; i++) {
printf("%lld ", ans[i]);
}
}
二、强连通分量相关
(一)Tarjan求强连通分量
1.一些概念:(1)树边:DFS时 走的边;
(2)返祖边:原有向图中 从DFS生成树的后代指向祖先 的边;
(3)前向边:与返祖边相反 ;
(4)横叉边:原有向图中 在DFS生成树中指向非直系亲属 的边;
(5)强连通:两个点互相可达 ;
(6)强连通子图:一个有向子图内,所有点两两之间均强连通 ;
(7)分量:极大子图,满足要求且不能再加入任何一个点。
2.一些结论:(1)强连通分量是它是一棵树 的充分不必要 条件;
(2)所有强连通分量互不交叉 ,因为如果交叉那么它们可以形成一个更大的强连通分量。
3.思路:(1)维护两个数组,dfn存DFS序 ,low存u能访问到的最早的已经在栈里 的时间戳;
(2)访问到之后更新dfn[u]和low[u]为当前时间戳 ,因为刚刚访问到这棵子树所以最早的就是u本身 ,节点入栈 ;
(3)之后进行循环 :如果没有访问过v ,递归 并更新low[u]=min(low[u],low[v]) ;否则如果访问过而且在栈里面,更新low[u]=min(low[u],dfn[v]或low[v]) ;
(4)之后如果dfn[u]=low[u] ,则形成了一个强连通分量,所有点出栈+记录。
4.问题:(1)为什么如果访问过但是不在栈里 不进行处理?
(2)为什么dfn[u]=low[u]则形成了一个强连通分量?
(3)为什么还在栈里面的一定可以到达u?
(4)为什么low[u]可以更新为low[v]?
解答:(1)访问过不在栈里说明出栈了 ,出栈了说明该点已经形成强连通分量了 ;
(2)必要 性:形成强连通分量说明根节点u无法回溯 ,即dfn[u]=low[u];充分 性:遍历完之后dfn[u]=low[u],说明u无法回溯 ,至少它自己是强连通分量 ,而栈里的都可以到达u,u也可以到达它们,那么它们彼此互相强连通 ,因此整个是强连通子图 ,而如果不是强连通分量,那么会存在一个点与之强连通,这样的点一定不在其他分量中,一定在栈里,与假设矛盾,因此是强连通分量 ;
(3)如果到不了u,那么v和u不强连通 ,已经事先出栈 了;
(4)因为既然u可以到达v,那么v可到达的点u也可以到达。
5.代码如下:
cpp
#include <bits/stdc++.h>
#define int long long
using namespace std;
bool in_stack[10010], vis[10010];
int n, m, u, v, dfn[10010], low[10010], scc[10010], timer = 0, sc = 0;
stack<int> stk;
vector<int> g[10010];
vector<int> ans[10010];
void tarjan(int u) {
dfn[u] = low[u] = ++timer;
stk.push(u);
in_stack[u] = 1;
for (int v : g[u]) {
if (!dfn[v]) {
tarjan(v);
low[u] = min(low[u], low[v]);
} else if (in_stack[v]) {
low[u] = min(low[u], dfn[v]);
}
}
if (dfn[u] == low[u]) {
sc++;
while (1) {
int v = stk.top();
stk.pop();
in_stack[v] = 0;
scc[v] = sc;
if (v == u)
break;
}
}
}
signed main() {
scanf("%lld%lld", &n, &m);
for (int i = 1; i <= m; i++) {
scanf("%lld%lld", &u, &v);
g[u].push_back(v);
}
for (int i = 1; i <= n; i++) {
if (!dfn[i])
tarjan(i);
}
for (int i = 1; i <= n; i++) {
ans[scc[i]].push_back(i);
}
printf("%lld\n", sc);
for (int i = 1; i <= n; i++) {
int t = scc[i];
if (vis[t])
continue;
vis[t] = 1;
for (int j = 0; j < ans[t].size(); j++)
printf("%lld ", ans[t][j]);
printf("\n");
}
}
(二)缩点
1.思路:(1)先求解强连通分量 ;
(2)每个强连通分量缩为一个点。
2.注意:是否可以缩点要视题目而定。
3.代码如下:
cpp
#include <bits/stdc++.h>
#define int long long
using namespace std;
bool in_stack[10010], vis[10010];
int n, m, u, v, dfn[10010], low[10010], scc[10010], timer = 0, sc = 0;
stack<int> stk;
vector<int> g[10010];
vector<int> ans[10010];
void tarjan(int u) {
dfn[u] = low[u] = ++timer;
stk.push(u);
in_stack[u] = 1;
for (int v : g[u]) {
if (!dfn[v]) {
tarjan(v);
low[u] = min(low[u], low[v]);
} else if (in_stack[v]) {
low[u] = min(low[u], dfn[v]);
}
}
if (dfn[u] == low[u]) {
sc++;
while (1) {
int v = stk.top();
stk.pop();
in_stack[v] = 0;
scc[v] = sc;
if (v == u)
break;
}
}
}
signed main() {
scanf("%lld%lld", &n, &m);
for (int i = 1; i <= m; i++) {
scanf("%lld%lld", &u, &v);
g[u].push_back(v);
}
for (int i = 1; i <= n; i++) {
if (!dfn[i])
tarjan(i);
}
for (int i = 1; i <= n; i++) {
ans[scc[i]].push_back(i);
}
printf("%lld\n", sc);
for (int i = 1; i <= n; i++) {
int t = scc[i];
if (vis[t])
continue;
vis[t] = 1;
for (int j = 0; j < ans[t].size(); j++)
printf("%lld ", ans[t][j]);
printf("\n");
}
}
三、双连通分量相关
(一)点双连通分量相关
[一]割点
1.定义:删去以后两边不再连通的点。
2.思路:(1)对于非根 ,如果low[v] >= dfn[u] ,则**(u, v)是割点** ;
(2)对于根 ,如果其孩子数超过1 ,则根和孩子们都是割点。
3.证明:(1)必要性:非根时,如果(u, v)是割点,则对于v来说无法与u的祖先连通 ,low[v] >= dfn[u] ;是根时,如果只有1个孩子,那么不可能是割点 ,因此超过1个 ;
(2)充分性:非根时,low[v] >= dfn[u],则v回溯不到u的祖先 ,因而删掉(u, v)之后无法连通;是根时,孩子数超过1个 ,则删掉任何一对几棵子树均无法连通。
4.代码如下:
cpp
#include <bits/stdc++.h>
#define int long long
#define MAXN 100010
using namespace std;
bool flag[MAXN];
int n, m, x, y, tms = 0, dfn[MAXN], low[MAXN];
vector<int> g[MAXN];
void tarjan(int u, int pa) {
dfn[u] = low[u] = ++tms;
int child = 0;
for (int v : g[u]) {
if (!dfn[v]) {
child++;
tarjan(v, u);
low[u] = min(low[u], low[v]);
if (u != pa && low[v] >= dfn[u])
flag[u] = 1;
}
else
low[u] = min(low[u], dfn[v]);
}
if (u == pa && child > 1)
flag[u] = 1;
}
signed main() {
scanf("%lld%lld", &n, &m);
for (int i = 1; i <= m; i++) {
scanf("%lld%lld", &x, &y);
g[x].push_back(y);
g[y].push_back(x);
}
for (int i = 1; i <= n; i++) {
if (!dfn[i])
tarjan(i, i);
}
vector<int> ans;
for (int i = 1; i <= n; i++)
if (flag[i])
ans.push_back(i);
printf("%lld\n", (int)ans.size());
for (int i = 0; i < ans.size(); i++)
printf("%lld%c", ans[i], " \n"[i == ans.size() - 1]);
return 0;
}
[二]点双连通分量
1.定义:删去任何一个点 仍能连通 的无向图分量。
2.思路:每次找到割点,割点之前栈里的点构成点双连通分量。
3.证明:(1)首先可知,在DFS生成树 上BCC一定是棵树 ;
(2)然后证法几乎和上面SCC一样。
4.代码如下:
cpp
#include <bits/stdc++.h>
#define int long long
#define MAXN 2000010
using namespace std;
bool flag[MAXN];
int n, m, x, y, tms = 0, ind = 0, dfn[MAXN], low[MAXN];
vector<int> g[MAXN], bcc[MAXN];
stack<int> stk;
void tarjan(int u, int pa) {
dfn[u] = low[u] = ++tms;
stk.push(u);
if (!g[u].size()) {
bcc[++ind].push_back(u);
return;
}
for (int v : g[u]) {
if (!dfn[v]) {
tarjan(v, u);
low[u] = min(low[u], low[v]);
}
else
low[u] = min(low[u], dfn[v]);
}
if (u != pa && low[u] >= dfn[pa]) {
ind++;
while (1) {
int x = stk.top();
stk.pop();
bcc[ind].push_back(x);
if (u == x)
break;
}
bcc[ind].push_back(pa);
}
}
signed main() {
scanf("%lld%lld", &n, &m);
for (int i = 1; i <= m; i++) {
scanf("%lld%lld", &x, &y);
if (x == y)
continue;
g[x].push_back(y);
g[y].push_back(x);
}
for (int i = 1; i <= n; i++) {
if (!dfn[i])
tarjan(i, i);
}
printf("%lld\n", ind);
for (int i = 1; i <= ind; i++) {
printf("%lld ", bcc[i].size());
for (int j = 0; j < bcc[i].size(); j++) {
printf("%lld ", bcc[i][j]);
}
printf("\n");
}
return 0;
}
(二)边双连通分量相关
[一]割边/桥
1.定义:删去以后两边不再连通的边。
2.思路:如果low[v] > dfn[u] ,则**(u, v)是桥**。
3.证明:(1)必要性:(u, v)是桥,则对于v来说无法与u所在的连通块连通 ,low[v] > dfn[u] ;
(2)充分性:low[v] > dfn[u],则v能回溯到的最早的点不可能与u有直系关系/只能是它自己,即回溯不到u,因而删掉之后无法连通。
4.代码如下:
cpp
#include <bits/stdc++.h>
#define int long long
#define MAXN 2000010
using namespace std;
bool flag[MAXN];
int n, m, x, y, tms = 0, dfn[MAXN], low[MAXN];
vector<pair<int, int>> g[MAXN];
void tarjan(int u, int idx) {
dfn[u] = low[u] = ++tms;
for (auto p : g[u]) {
int v = p.first, id = p.second;
if (!dfn[v]) {
tarjan(v, id);
low[u] = min(low[u], low[v]);
if (low[v] > dfn[u])
flag[id] = 1;
}
else if (idx != id)
low[u] = min(low[u], dfn[v]);
}
}
signed main() {
scanf("%lld%lld", &n, &m);
for (int i = 1; i <= m; i++) {
scanf("%lld%lld", &x, &y);
g[x].push_back({y, i});
g[y].push_back({x, i});
}
for (int i = 1; i <= n; i++) {
if (!dfn[i])
tarjan(i, 0);
}
vector<int> ans;
for (int i = 1; i <= m; i++)
if (flag[i])
ans.push_back(i);
printf("%lld\n", (int)ans.size());
for (int i = 0; i < ans.size(); i++)
printf("%lld ", ans[i]);
}
[二]边双连通分量
1.定义:删去任何一条边 仍能连通 的无向图分量。
2.结论:从任意点DFS,不走桥 能到达的所有点都是该EBCC。
3.证明:(1)必要性:既然是EBCC ,那么删掉任何一条边都仍然连通 ,即没有桥 ;
(2)充分性:不存在桥 ,一定是边双连通子图 ,假设不是分量那么一定存在某些非桥对岸的点未到达 过,而这些点既然不存在桥一定会遍历 与假设矛盾,所以是EBCC。
4.代码如下:
cpp
#include <bits/stdc++.h>
#define int long long
#define MAXN 2000010
using namespace std;
bool flag[MAXN];
int n, m, x, y, tms = 0, ind = 0, dfn[MAXN], low[MAXN], ebcc[MAXN];
vector<pair<int, int>> g[MAXN];
vector<int> bcc[MAXN];
void tarjan(int u, int idx) {
dfn[u] = low[u] = ++tms;
for (auto p : g[u]) {
int v = p.first, id = p.second;
if (!dfn[v]) {
tarjan(v, id);
low[u] = min(low[u], low[v]);
if (low[v] > dfn[u])
flag[id] = 1;
}
else if (idx != id)
low[u] = min(low[u], dfn[v]);
}
}
void dfs(int u) {
ebcc[u] = ind;
bcc[ind].push_back(u);
for (auto p : g[u]) {
int v = p.first, id = p.second;
if (!flag[id] && !ebcc[v])
dfs(v);
}
}
signed main() {
scanf("%lld%lld", &n, &m);
for (int i = 1; i <= m; i++) {
scanf("%lld%lld", &x, &y);
g[x].push_back({y, i});
g[y].push_back({x, i});
}
for (int i = 1; i <= n; i++) {
if (!dfn[i])
tarjan(i, 0);
}
for (int i = 1; i <= n; i++) {
if (!ebcc[i]) {
ind++;
dfs(i);
}
}
printf("%lld\n", ind);
for (int i = 1; i <= ind; i++) {
printf("%lld ", bcc[i].size());
for (int j = 0; j < bcc[i].size(); j++) {
printf("%lld ", bcc[i][j]);
}
printf("\n");
}
}