#include 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 res[N]; // 每个环中有哪些节点 int rl; // 环的数量游标 int vist[N]; // 某个点是不是访问过 int st[M]; // 边是不是访问过,用于无向图的成对变换优化 vector 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没有访问过,并且,是数据中出现过的点,比如1,2,5,6出现,3,4不参加讨论问题 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; }