Wizard′sTour
最初想法
考虑枚举每个边 (u,v), 设 u 连出的点中度数最小的点为 a, v 连出的点中度数最小的点为 b,
比较 u,a 与 v,b 的度数和大小, 贪心地选取度数较小的点, 得到一个三元路径,
使用 multiset 维护度数, 枚举边按以上方法找路径即可 .
代码纪念
正解部分
先从图中剥离出一颗树, 然后 DFS, 从底向顶处理, 设当前节点为 i, 度数为 cnti,
- 若 cnti 为偶数 2n , 则直接转化为 n 条路径 .
- 若 cnti 为奇数 2n−1, 则连向父亲的边保存, 其余构成 (2n−2)/2 条路径 .
这样构造, 可以使得最后至多留下一条边, 这条边是连向根节点的 .
实现部分
DFS 到一个点 k 时, 其连出去的点包含以下类型 ↓
- 儿子
- 使用了与父亲相连的边的儿子
- 没有使用 …
- 父亲
- 需要使用与父亲相连的边
- 不需要 …
- 非子非父
- 已经连过边
- 没有…
- 注意根节点没有父亲 .
- 可以使用类似网络流的建图方式删边 .
- 使用标记数组 vis[] 辅助建树 .
- 使用标记数组 usefa[] 记录是否使用了 fa 的边 .
#include<bits/stdc++.h>
#define reg register
#define pb push_back
int read(){
char c;
int s = 0, flag = 1;
while((c=getchar()) && !isdigit(c))
if(c == '-'){ flag = -1, c = getchar(); break ; }
while(isdigit(c)) s = s*10 + c-'0', c = getchar();
return s * flag;
}
const int maxn = 200005;
int N;
int M;
int num0;
int A_cnt;
int cnt[maxn];
int vis[maxn];
int head[maxn];
int Ans[maxn][3];
int use_fa[maxn];
std::vector <int> A[maxn];
struct Edge{ int nxt, to; } edge[maxn<<1];
void Add(int from, int to){
edge[++ num0] = (Edge){ head[from], to };
head[from] = num0;
}
void DFS(int k, int fa, int have_fa){
vis[k] = 1;
for(reg int i = head[k]; i; i = edge[i].nxt){
int to = edge[i].to;
if(!to) continue ;
edge[i].to = edge[i^1].to = 0;
if(!vis[to]){
DFS(to, k, 1);
if(!use_fa[to]) A[k].pb(to); // to使用了与父亲k相连的边
}else A[k].pb(to);
}
if(have_fa) A[k].pb(fa);
int size = A[k].size();
if(size <= 1) return ;
use_fa[k] = !(size & 1);
for(reg int i = 0; i+1 < size; i += 2)
Ans[++ A_cnt][0] = A[k][i], Ans[A_cnt][1] = k, Ans[A_cnt][2] = A[k][i+1];
}
int main(){
N = read(), M = read();
num0 = 1;
for(reg int i = 1; i <= M; i ++){
int u = read(), v = read();
Add(u, v), Add(v, u);
}
for(reg int i = 1; i <= N; i ++) if(!vis[i]) DFS(i, 0, 0);
printf("%d\n", A_cnt);
for(reg int i = 1; i <= A_cnt; i ++) printf("%d %d %d\n", Ans[i][0], Ans[i][1], Ans[i][2]);
return 0;
}