0925考试T2 欧拉回路
0925考试T2
题目大意:
给定一个任意的无向图,问最少画几笔,使得所有的边被画过一次且仅一次。
欧拉回路。
我们称有偶数条边连着的点叫偶数点,有奇数条边连着的点叫奇数点。
欧拉路:有一条路径可以经过所有边并且不重复。欧拉回路:闭合的欧拉路。
首先我们要知道:无向图中存在欧拉回路的条件是所有点都是偶数点,因为有进就有出嘛。
对于这个题,有好多个联通块,每个联通块是一个无向图,每个联通块的答案是独立的。对于某个无向图既有奇数点又有偶数点,我们可以把所有奇数点变为偶数点。把奇数点变为偶数点肯定要加边,我们把这些边标记一下,相当于在整个欧拉回路里是一个断点,就是由这些新加的边确定到底要画几笔。
那具体怎么加呢?我们让奇数点之间两两连边,至少要加\(max(\frac{k}{2}, 1)\)条边,\(k\)是奇数点的个数,因为每加一条边就会消去两个奇数点。把这些加的边叫做虚边。
我们在\(dfs\)的时候,遇到一条边就标记一条,表示下次不会搜这条边了。如果碰到一个虚边,我们就让次数加一。
#include <bits/stdc++.h>
using namespace std;
inline double read() {
double s = 0, f = 1; char ch;
while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
for(s = ch ^ 48;isdigit(ch = getchar()); s = (s * 10) + (ch ^ 48));
return s * f;
}
const int N = 1e5 + 5;
int T, n, m, cnt, tot;
int vis[N], out[N], head[N];
vector <int> a[N];
struct edge { int f, to, id, nxt; } e[N << 3];
void clear() {
cnt = 1, tot = 0; //cnt = 1为了成对变换
for(int i = 0;i <= n; i++) head[i] = vis[i] = out[i] = 0;
}
void add(int x, int y, int id) {
e[++cnt].nxt = head[x]; head[x] = cnt; e[cnt].to = y; e[cnt].id = id; e[cnt].f = 0;
}
void dfs(int x) {
vis[x] = 1;
for(int i = head[x]; i ; i = e[i].nxt) {
int y = e[i].to; int id = e[i].id;
if(!e[i].f) {
e[i].f = e[i ^ 1].f = 1; //将这条边和反向边标记
dfs(y);
if(id) a[tot].push_back(-id); //-id是为了输出顺序是对的
else tot ++; //扫到虚边
}
}
}
int main() {
T = read();
while(T --> 0) {
n = read(); m = read(); clear();
for(int i = 1, x, y;i <= m; i++) {
x = read(); y = read(); out[x] ++; out[y] ++;
add(x, y, i); add(y, x, -i);
}
int last = 0;
for(int i = 1;i <= n; i++)
if(out[i] & 1)
if(last) {
add(last, i, 0); add(i, last, 0); last = 0;
} //奇数点两两连边
else last = i;
for(int i = 1;i <= n; i++)
if(!vis[i] && (out[i] & 1)) {
tot ++; dfs(i); tot--; //因为虚边是最后一次加入的,tot会多加一次
}
for(int i = 1;i <= n; i++)
if(!vis[i] && out[i]) {
tot ++; dfs(i);
}
printf("%d\n", tot - 1);
for(int i = 1;i <= tot; i++) {
printf("%d", (int)a[i].size());
for(int j = 0;j < (int)a[i].size(); j++)
printf(" %d", a[i][j]);
printf("\n");
a[i].clear();
}
}
fclose(stdin); fclose(stdout);
return 0;
}