题解:CF858F Wizard's Tour
本文在编写完成后使用了 Qwen3-Max 进行润色。
这是一道非常巧妙的好题。
我们考虑通过一次 DFS 来完成整个构造过程。
在 DFS 的过程中,我们可以自然地构建出一棵 DFS 树。对于树中除根节点外的任意一个节点 $ v $,其连出的边可以分为三类:
- 父向边:连接 $ v $ 与其父节点;
- 子向边:连接 $ v $ 与其子节点;
- 返祖边:连接 $ v $ 与某个祖先(非父节点)。
关于返祖边的处理
本题的关键在于:我们不关心一个点被经过多少次,只关心每条边是否被使用恰好一次。因此,返祖边可以被“转化”为一条虚拟的子向边来处理(你可以理解为把祖先“拉下来”当作一个叶子儿子)。(祖宗:好好好,你小子把我薅下来当你儿子,还是个叶子?)
这样做是安全的,因为这个“假儿子”不会“夺舍”这条边——它没有其他出边可供匹配,所以这条边一定可以参与当前节点 $ v $ 的匹配。
关于子向边的处理
对于真实的子节点,我们在递归处理完子树后,就能知道该子节点是否会“夺舍”它与 $ v $ 之间的那条边(即子节点是否已用这条边进行匹配)。
- 如果子节点已经用了这条边,那么 $ v $ 就不能再用;
- 否则,这条边对 $ v $ 是可用的。
关于父向边的决策
当我们处理完所有子向边和返祖边后,统计当前节点 $ v $ 可用于匹配的边数(不包括父向边)。
- 如果这个数量是偶数,说明 $ v $ 已经能内部配对完毕,那么父向边就可以留给父节点使用;
- 如果是奇数,则 $ v $ 会“多出”一条边无法配对,此时就将父向边“夺舍”过来,与这条多余的边配成一对。
这样自底向上贪心地构造,即可保证每条边恰好被使用一次,并且所有路径长度均为 2(即合法的“Wizard's Tour”)。
通过一次 DFS 即可完成全部匹配,时间复杂度为 $ O(n + m) $,简洁而高效。
(什么?你说我不给代码?受着)
代码
#include<bits/stdc++.h>
using namespace std;
constexpr int N=200005;
int n,m;
bitset<N> vis;
bitset<N> use;
struct edge{
int v,i;
};
vector<edge> g[N];
stringstream ans;
int cnt;
bool dfs(int u,int f,int fi){
int lzy=0,lzyi=0;
vis[u]=1;
for(auto [v,i]:g[u]){
if(v==f||use[i])continue;
bool enable=vis[v]?1:!dfs(v,u,i);
if(enable){
if(lzy){
cnt++;
ans<<lzy<<" "<<u<<" "<<v<<"\n";
use[i]=use[lzyi]=1;
lzy=lzyi=0;
}else{
lzy=v;lzyi=i;
}
}
}
if(lzy&&f){
cnt++;
ans<<lzy<<" "<<u<<" "<<f<<"\n";
use[fi]=use[lzyi]=1;
return 1;
}else{
return 0;
}
}
int main(){
cin>>n>>m;
for(int i=1;i<=m;i++){
int a,b;
cin>>a>>b;
g[a].push_back({b,i});
g[b].push_back({a,i});
}
for(int i=1;i<=n;i++){
if(!vis[i]){
dfs(i,0,0);
}
}
cout<<cnt<<"\n"<<ans.rdbuf();
return 0;
}

浙公网安备 33010602011771号