8.30 巫师之旅
题意
给出一张\(n\)个点\(m\)条边的无向图,定义一次旅行为任选一个点\(x\)作为起点,再走到一个与\(x\)直接连接的点\(y\),再走到一个与\(y\)直接连接的点\(z\)结束本次旅行
要求每条边(无向边)只能被经过一次,而点没有限制
请计算出可能进行的最多的旅游次数并输出其中任意一种方案
解法
这题考试时想出来了,但打得代码又臭又长。。
可以玩一玩,发现对于一个连通块,它的答案就是其中的边数除以\(2\)
本题的难点在于方案的输出
考虑对于一颗树,我们怎样输出方案
可以规定合并的顺序为先儿子,后父亲
即如果该点有儿子,则把儿子两两进行合并;如果该点有奇数个儿子,则把最后一个儿子与父亲合并并打上标记
这样一定能构造出一个合法的方案,因为每一个结点一定是由它的父亲进行更新的
考虑更复杂的情况,即原题所要求的图
我们可以发现图其实就是树的情况拉上了很多条非树边
非树边可以与儿子放在一起,规则同上
这样就能构造出一个合法解了
代码
就不放考场上打得\(3kb\)奇丑无比的代码了。。
#include <cstdio>
#include <queue>
#include <cstring>
using namespace std;
const int N = 4e5 + 10;
int n, m;
int cap = 1;
int head[N], to[N << 1], nxt[N << 1];
int cnt, num;
int bfn[N], fa[N], can[N], ans[N][3];
int book[N], vis[N];
queue<int> que;
inline void add(int x, int y) {
to[++cap] = y, nxt[cap] = head[x], head[x] = cap;
}
void init(int rt) {
cnt = 0;
que.push(rt), book[rt] = 1;
while (!que.empty()) {
int x = que.front(); que.pop();
bfn[++cnt] = x;
for (int i = head[x]; i; i = nxt[i]) {
if (book[to[i]]) continue;
que.push(to[i]);
fa[to[i]] = x, book[to[i]] = 1;
}
}
}
void solve(int rt) {
init(rt);
for (int i = cnt; i >= 1; --i) {
int x = bfn[i], tot = 0;
for (int j = head[x]; j; j = nxt[j])
if (!vis[j] && to[j] != fa[x]) can[++tot] = j;
for (int j = head[x]; j; j = nxt[j])
if (!vis[j] && to[j] == fa[x]) can[++tot] = j;
for (int j = 1; j + 1 <= tot; ++j) {
vis[can[j]] = vis[can[j] ^ 1] = 1;
ans[++num][0] = to[can[j]];
++j;
ans[num][1] = x;
vis[can[j]] = vis[can[j] ^ 1] = 1;
ans[num][2] = to[can[j]];
}
}
}
int main() {
scanf("%d%d", &n, &m);
int u, v;
for (int i = 1; i <= m; ++i) {
scanf("%d%d", &u, &v);
add(u, v), add(v, u);
}
for (int i = 1; i <= n; ++i)
if (!book[i]) solve(i);
printf("%d\n", num);
for (int i = 1; i <= num; ++i) printf("%d %d %d\n", ans[i][0], ans[i][1], ans[i][2]);
return 0;
}