You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

121 lines
4.9 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10, M = 2e6 + 10;
// 链式前向星
int e[M], h[N], idx, w[M], ne[M];
void add(int a, int b, int c = 0) {
e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}
int d[N]; // 度
vector<int> res[N]; // 每个环中有哪些节点
int rl; // 环的数量游标
int vist[N]; // 某个点是不是访问过
int st[M]; // 边是不是访问过,用于无向图的成对变换优化
vector<int> stk; // 找不用的栈
int instk[N]; // 节点i是不是在栈内
void dfs(int u) {
// u节点已访问过之所以需要vist[u]这样的状态数组,本质上是因为本题是一张图中多组欧拉回路,
// 没跑过的点才需要当做起点跑欧拉回路,需要记录哪个跑过哪个没跑过
vist[u] = 1;
for (int i = h[u]; ~i; i = h[u]) { // 枚举u节点的每个出边i
h[u] = ne[i]; // 删边优化
if (st[i]) continue; // 此边访问过,不用再讨论,其实,这是在处理成对变换的另一边
st[i] = st[i ^ 1] = 1; // 无向图,成对标识已被访问过
int v = e[i]; // 节点u通过边i指向点v
// cout << "u=" << u << ",v=" << v << ",instk[1]=" << instk[1] << endl;
/*
u=1,v=7,instk[1]=0
u=7,v=3,instk[1]=0
u=3,v=6,instk[1]=0
u=6,v=4,instk[1]=0
u=4,v=1,instk[1]=0
u=3,v=5,instk[1]=1
u=5,v=2,instk[1]=1
u=2,v=3,instk[1]=1
u=9,v=13,instk[1]=0
u=13,v=14,instk[1]=0
u=14,v=9,instk[1]=0
*/
// 代码跟踪表示,现在黄海画的图节点走向是正确的
dfs(v); // 深搜v=e[i]这个点
if (instk[v]) { // 如果v点在栈中说明找到了行进路线中的环
res[++rl].push_back(v); // v记录到rl号环中
while (stk.back() != v) { // 一直把栈顶元素弹出直到找出首次进入的那个v也就是通过栈找环
res[rl].push_back(stk.back()); // 将环中的节点记录到res[rl]这个结果集中去
instk[stk.back()] = 0; // 标识栈顶元素已出栈
stk.pop_back(); // 栈顶元素出栈
}
res[rl].push_back(v); // 由于上面使用的是stk.back()!=v,这样是为了保持v还在栈中让这个点重复命使用可以参考图2的点3
} else { // 如果不在栈内
stk.push_back(v); // 入栈
instk[v] = 1; // 标识在栈内
}
}
}
int main() {
#ifndef ONLINE_JUDGE
freopen("P3520.in", "r", stdin);
#endif
memset(h, -1, sizeof h); // 初始化链式前向星
int n, m;
scanf("%d %d", &n, &m); // n个顶点m条边
int a, b, s, t;
while (m--) {
scanf("%d %d %d %d", &a, &b, &s, &t); // a到b有一条边颜色初始值是s,目标终止值是t
if (s ^ t) { // 如果s与t不一样,建边。欧拉图需要每边只走一次
add(a, b), add(b, a); // 双向建边,因为可以正着走也可以反着走但正反边只能走1次
d[a]++, d[b]++; // 维护度
}
}
// 检查是否某个点的度是奇数,这样没有欧拉回路
for (int i = 1; i <= n; i++)
if (d[i] % 2) {
puts("NIE");
exit(0);
}
// 遍历每个节点
for (int i = 1; i <= n; i++)
if (!vist[i] && d[i]) { // 如果节点i没有访问过并且是数据中出现过的点比如1256出现34不参加讨论问题
dfs(i); // 对节点i进行深搜找简单环
/*简单环分两种情况:
① 出发点在内的简单环
② 在行进路线上出现的简单环
这两种情况处理办法不一样,需要分别对待,下面就是情况①
由于上面已经通过一笔画的定理排除掉了非欧拉回路的可能,现在的图肯定是欧拉回路
那么在dfs过程中我们可以把路径上的环处理掉那么一定有从起点出发的环还在栈中需要单独处理
*/
res[++rl].push_back(i);
while (stk.size()) {
res[rl].push_back(stk.back());
instk[stk.back()] = 0;
stk.pop_back();
}
}
// 输出环的数量
printf("%d\n", rl);
// 遍历每个环
for (int i = 1; i <= rl; i++) {
printf("%d ", res[i].size() - 1); // 输出环中节点个数这里为什么要减1呢因为是环嘛首尾是一样的加入了两次
for (auto x : res[i]) printf("%d ", x);
puts("");
}
return 0;
}