dfn[x]
:结点 x 第一次被访问的时间戳 (dfs number); low[x]
:结点 x 所能访问到的点的 dfn 值的最小值. 这里的树指的是 DFS 树. 所有结点按 dfn 排序即可得 dfs 序列
一个结点的子树内结点的 dfn 都大于该结点的 dfn。从根开始的一条路径上的 dfn 严格递增。一棵 DFS 树被构造出来后,考虑图中的非树边。前向边 (forward edge):祖先→儿子。后向边 (backward edge):儿子→祖先。横叉边 (cross edge):没有祖先—儿子关系的。注意:横叉边只会往 dfn 减小的方向连接。在无向图中,没有横叉边(因为无向图的横插边一定有)、
在构造 dfs 树的时候,称 d f s dfs dfs 尚未搜索到的边为树枝边
#include
#include
#include
#include
using namespace std;
const int maxn = 10010, maxm = 50010;
int h[maxn], e[maxm], ne[maxm], idx;
int N, M, dfn[maxn], low[maxn];
int id[maxn], sz[maxn], out[maxn], timestamp, scc_cnt;
bool in_stk[maxn];
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
stack<int> stk;
void tarjan(int u) {
//千万别写成 timestamp++ !!!
dfn[u] = low[u] = ++timestamp;
stk.push(u), in_stk[u] = true;
for (int i = h[u]; i != -1; i = ne[i]) {
int v = e[i];
if (!dfn[v]) {
tarjan(v);
low[u] = min(low[u], low[v]);
}
else if (in_stk[v]) low[u] = min(low[u], dfn[v]);
}
if (dfn[u] == low[u]) {
scc_cnt++;
int v;
do {
v = stk.top(); stk.pop();
in_stk[v] = false;
id[v] = scc_cnt;
sz[scc_cnt]++;
} while (v != u);
}
}
void solve() {
for (int i = 1; i <= N; i++) {
if (!dfn[i]) {
tarjan(i);
}
}
for (int u = 1; u <= N; u++) {
for (int i = h[u]; i != -1; i = ne[i]) {
int v = e[i];
int a = id[u], b = id[v];
if (a != b) out[a]++;
}
}
int zero = 0, sum = 0;
for (int i = 1; i <= scc_cnt; i++) {
if (!out[i]) {
zero++;
sum += sz[i];
if (zero > 1) {
sum = 0;
break;
}
}
}
printf("%d\n", sum);
}
int main() {
scanf("%d%d", &N, &M);
memset(h, -1, sizeof h);
for (int i = 0; i < M; i++)
{
int a, b;
scanf("%d%d", &a, &b);
add(a, b);
}
solve();
return 0;
}
#include
#include
#include
#include
using namespace std;
const int maxn = 5010, maxm = 20010;
int h[maxn], ne[maxm], e[maxm], idx;
int id[maxn], low[maxn], dfn[maxn], timestamp, dcc_cnt;
int N, M, d[maxn];
bool is_bridge[maxm];
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
stack<int> stk;
// from 记载从哪条边来的。
void tarjan(int u, int from) {
low[u] = dfn[u] = ++timestamp;
stk.push(u);
for (int i = h[u]; i != -1; i = ne[i]) {
int v = e[i];
if (!dfn[v]) {
tarjan(v, i);
low[u] = min(low[u], low[v]);
//dfn[u] < low[v] 等价于 u 与 v 之间的边是桥。
if (dfn[u] < low[v]) {
//双向边的编号是两两成对的。
is_bridge[i] = is_bridge[i ^ 1] = true;
}
}
else if (i != (from ^ 1)) low[u] = min(low[u], dfn[v]);
}
if (dfn[u] == low[u]) {
++dcc_cnt;
int v;
do {
v = stk.top(); stk.pop();
id[v] = dcc_cnt;
} while (v != u);
}
}
int main() {
scanf("%d%d", &N, &M);
memset(h, -1, sizeof h);
for (int i = 0; i < M; i++) {
int a, b;
scanf("%d%d", &a, &b);
add(a, b), add(b, a);
}
tarjan(1, -1);
for (int i = 0; i < idx; i++) {
if (is_bridge[i]) d[id[e[i]]]++;
}
int cnt = 0;
for (int i = 1; i <= dcc_cnt; i++) {
if (d[i] == 1) cnt++;
}
printf("%d\n", (cnt + 1) / 2);
return 0;
}
#include
#include
#include
using namespace std;
const int maxn = 10010, maxm = 30010;
int h[maxn], e[maxm], ne[maxm], idx;
int N, M, res, blocks, root;
int dfn[maxn], low[maxn], timestamp;
void tarjan(int u) {
dfn[u] = low[u] = ++timestamp;
int cnt = 0;
for (int i = h[u]; i != -1; i = ne[i]) {
int v = e[i];
if (!dfn[v]) {
tarjan(v);
low[u] = min(low[u], low[v]);
if (low[v] >= dfn[u]) cnt++;
}
else low[u] = min(low[u], dfn[v]);
}
if (u != root) cnt++;
res = max(res, cnt);
}
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
int main() {
while (scanf("%d%d", &N, &M) && N) {
memset(dfn, 0, sizeof dfn);
memset(h, -1, sizeof h);
timestamp = idx = 0;
for (int i = 0; i < M; i++) {
int a, b;
scanf("%d%d", &a, &b);
add(a, b), add(b, a);
}
res = blocks = 0;
for (root = 0; root < N; root++) {
if (!dfn[root]) {
blocks++;
tarjan(root);
}
}
printf("%d\n", blocks + res - 1);
}
return 0;
}
#include
#include
#include
#include
#include
using namespace std;
typedef unsigned long long ll;
const int maxn = 1010, maxm = 1010;
int h[maxn], e[maxm], ne[maxm], idx;
int dfn[maxn], low[maxn], dcc_cnt, timestamp;
int N, M, root, kase;
bool is_cut[maxn];
vector<int> dcc[maxn]; //这个存的是每一个连通分量含哪些节点。
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
stack<int> stk;
void tarjan(int u) {
dfn[u] = low[u] = ++timestamp;
stk.push(u);
if (u == root && h[u] == -1) {
dcc_cnt++;
dcc[dcc_cnt].push_back(u);
return;
}
int cnt = 0;
for (int i = h[u]; i != -1; i = ne[i]) {
int v = e[i];
if (!dfn[v]) {
tarjan(v);
low[u] = min(low[u], low[v]);
if (dfn[u] <= low[v]) {
cnt++;
if (u != root || cnt > 1) is_cut[u] = true;
dcc_cnt++;
int y;
do {
y = stk.top(); stk.pop();
dcc[dcc_cnt].push_back(y);
} while (y != v);
dcc[dcc_cnt].push_back(u);
}
}
else low[u] = min(low[u], dfn[v]);
}
}
int main() {
while (scanf("%d", &M) && M) {
for (int i = 1; i <= dcc_cnt; i++) dcc[i].clear();
memset(h, -1, sizeof h);
memset(dfn, 0, sizeof dfn);
memset(is_cut, 0, sizeof is_cut);
while (stk.size()) stk.pop();
idx = N = dcc_cnt = timestamp = 0;
for (int i = 0; i < M; i++) {
int a, b;
scanf("%d%d", &a, &b);
add(a, b), add(b, a);
N = max(a, N), N = max(b, N);
}
for (root = 1; root <= N; root++) {
if (!dfn[root]) tarjan(root);
}
ll num = 1;
int res = 0;
for (int i = 1; i <= dcc_cnt; i++) {
int cnt = 0, sz = dcc[i].size();
for (int j = 0; j < sz; j++) {
if (is_cut[dcc[i][j]]) cnt++;
}
if (cnt == 0) {
if (sz > 1) res += 2, num *= (sz - 1) * sz / 2;
else res++;
}
else if (cnt == 1) res++, num *= sz - 1;
}
printf("Case %d: %d %llu\n", ++kase, res, num);
}
return 0;
}
如果图 G G G 中的一个路径包括每个边恰好一次,则该路径称为欧拉路径 ( E u l e r p a t h ) (Euler\ path) (Euler path)。
如果一个回路是欧拉路径,则称为欧拉回路 ( E u l e r c i r c u i t ) (Euler\ circuit) (Euler circuit)。
具有欧拉回路的图称为欧拉图(简称 E E E 图)。具有欧拉路径但不具有欧拉回路的图称为半欧拉图。
无向图(图必须是连通图):
printf("%.f:%02.f\n", hours, minutes);
#include
#include
#include
using namespace std;
const int maxn = 100010, maxm = 400010;
int h[maxn], e[maxm], ne[maxm], idx;
bool used[maxm];
int N, M, ans[maxm / 2], cnt, type, din[maxn], dout[maxn];
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
void dfs(int u) {
//引用千万不要忘!用引用的目的其实是为了针对自环特别多的情况,可以把父结点的那条边也删掉。
for (int& i = h[u]; i != -1;) {
if (used[i]) {
h[u] = ne[i]; ///注意这个删除节点的操作还是很简洁的!
continue;
}
/*
这个used[i] = true 必须要带上。因为尽管看似这条边已经删掉了,但是只是对于子节点
而言。而对于父结点还没有删掉。
*/
used[i] = true;
if (type == 1) used[i ^ 1] = true;
int t;
if (type == 1) {
t = i / 2 + 1;
if (i & 1) t = -t;
}
else t = i + 1;
int v = e[i];
i = ne[i];
dfs(v);
ans[cnt++] = t;
}
}
int main() {
scanf("%d%d%d", &type, &N, &M);
memset(h, -1, sizeof h);
for (int i = 0; i < M; i++) {
int a, b;
scanf("%d%d", &a, &b);
add(a, b);
if (type == 1) add(b, a);
dout[a]++, din[b]++;
}
if (type == 1) {
for (int i = 1; i <= N; i++) {
if (din[i] + dout[i] & 1) {
printf("NO\n");
return 0;
}
}
}
else {
for (int i = 1; i <= N; i++) {
if (din[i] != dout[i]) {
printf("NO\n");
return 0;
}
}
}
//要从至少有一条边节点开始搜索
for (int i = 1; i <= N; i++) {
if (h[i] != -1) {
dfs(i);
break;
}
}
//cnt < M 意味着图中的边不连通。
if (cnt < M) {
printf("NO\n");
return 0;
}
printf("YES\n");
for (int i = cnt - 1; i >= 0; i--) printf("%d%c", ans[i], i == 0 ? '\n' : ' ');
return 0;
}
有向无环图至少存在一个入度为0的点,有环图一定不存在拓扑序。
若一个由图中所有点构成的序列A满足:对于图中的每条边(x, y),x在A中都出现在y之前,则称A是该图的一个拓扑序列。
有向无环图的拓扑序不一定是唯一的。
可以拓扑排序的图,等价于拓扑图,等价于有向无环图(DAG)。
步骤:
#include
#include
#include
const int maxn = 110, maxm = 5010;
using namespace std;
int h[maxn], e[maxm], ne[maxm], idx;
int N, ans[maxn], din[maxn], cnt;
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
void toposort() {
queue<int> que;
//首先,把所有入度为0的点加入队列。
for (int i = 1; i <= N; i++) {
if (!din[i]) {
ans[++cnt] = i;
que.push(i);
}
}
while (que.size()) {
int u = que.front(); que.pop();
//接着,每遍历一条u指出的边,就把u指向的顶点的入度减1。直到v的入度为0时再把v加进队列。
for (int i = h[u]; i != -1; i = ne[i]) {
int v = e[i];
din[v]--;
if (!din[v]) que.push(v), ans[++cnt] = v;
}
}
//若题目数据不能保证一定有解,则最后需要判断cnt == N(总共加进去了N个点),是的话就存在拓扑序,否则就不存在。
}
int main() {
scanf("%d", &N);
memset(h, -1, sizeof h);
for (int i = 1; i <= N; i++) {
int t;
while (cin >> t, t) add(i, t), din[t]++;
}
toposort();
for (int i = 1; i <= cnt; i++) printf("%d%c", ans[i], i == cnt ? '\n' : ' ');
return 0;
}
#include
#include
#include
using namespace std;
const int maxn = 100010, maxm = 200010;
int N, M;
int h[maxn], e[maxm], ne[maxm], idx;
int d[maxn], din[maxn], topo[maxn], cnt;
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
bool toposort() {
queue<int> que;
for (int i = 1; i <= N; i++) {
if (!din[i]) {
que.push(i);
topo[++cnt] = i;
}
}
while (que.size()) {
int u = que.front(); que.pop();
for (int i = h[u]; i != -1; i = ne[i]) {
int v = e[i];
din[v]--;
if (!din[v]) {
que.push(v);
topo[++cnt] = v;
}
}
}
return cnt == N;
}
void topo_longest_path() {
for (int i = 1; i <= N; i++) d[i] = 100;
/*
在拓扑序数组中求最长路一定小心!
一个是要遍历拓扑序数组,而不是遍历1到N的节点,另一个是,拓扑序中的下标也是1到N,别弄错!
*/
for (int j = 1; j <= cnt; j++) {
int u = topo[j];
for (int i = h[u]; i != -1; i = ne[i]) {
int v = e[i];
d[v] = max(d[v], d[u] + 1);
}
}
}
int main() {
memset(h, -1, sizeof h);
scanf("%d%d", &N, &M);
for (int i = 0; i < M; i++)
{
int a, b;
scanf("%d%d", &a, &b);
add(b, a);
din[a]++;
}
if (toposort())
{
topo_longest_path();
int ans = 0;
//注意这里还是1~N哈,因为节点的编号是从1到N的。
for (int i = 1; i <= N; i++) ans += d[i];
printf("%d\n", ans);
}
else printf("Poor Xed\n");
return 0;
}
void toposort() {
queue<int> que;
for (int i = 1; i <= N; i++) {
if (!din[i]) {
d[i] = 100;
que.push(i);
cnt++;
}
}
while (que.size()) {
int u = que.front(); que.pop();
for (int i = h[u]; i != -1; i = ne[i]) {
int v = e[i];
din[v]--;
if (!din[v]) {
que.push(v);
d[v] = d[u] + 1;
cnt++;
}
}
}
if (cnt != N) printf("Poor Xed\n");
else {
int ans = 0;
for (int i = 1; i <= N; i++) ans += d[i];
printf("%d\n", ans);
}
}
#include
#include
#include
#include
#include
using namespace std;
const int maxn = 30010, maxm = 30010;
int h[maxn], e[maxm], ne[maxm], idx;
int N, M, din[maxn];
bitset<maxn> ans[maxn];
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
void toposort() {
queue<int> que;
for (int i = 1; i <= N; i++) ans[i].set(i, 1);
for (int i = 1; i <= N; i++) {
if (!din[i]) {
que.push(i);
}
}
while (que.size()) {
int u = que.front(); que.pop();
for (int i = h[u]; i != -1; i = ne[i]) {
int v = e[i];
ans[v] |= ans[u], din[v]--;
if (din[v] == 0) que.push(v);
}
}
}
int main() {
memset(h, -1, sizeof h);
scanf("%d%d", &N, &M);
for (int i = 0; i < M; i++) {
int a, b;
scanf("%d%d", &a, &b);
add(b, a);
din[a]++;
}
toposort();
for (int i = 1; i <= N; i++) printf("%d\n", ans[i].count());
return 0;
}
toposort();
for (int j = N; j; j--) {
int u = topo[j];
f[u][u] = 1;
for (int i = h[u]; i != -1; i = ne[i]) {
int v = e[i];
f[u] |= f[v];
}
}