考虑对于一个解,将每对 \((e_1,e_2)\) 中 \(e_1\) 的终点权值 \(+1\),\(e_2\) 的起点权值 \(-1\),那么最终每个点的权值一定是 \(0\)。
考虑先将每条边的终点权值都 \(+1\),那么现在要做的就是选一些点将其起点和终点的权值都 \(-1\),使得最终每个点的权值为 \(0\),于是边的方向就不重要了。
因为是一颗树,考虑随便取一个点位根,从叶子节点开始贪心选。每次如果当前考虑的点权值 \(>0\),就从它连到父亲的边中随便选出若干个使其权值减到 \(0\),如果 \(<0\) 则无解。记录下选出的边,最终和没选择的边匹配即可。
参考代码:
#include<bits/stdc++.h>
#define mxn 300003
#define pb push_back
#define rep(i,a,b) for(int i=a;i<=b;++i)
#define rept(i,a,b) for(int i=a;i<b;++i)
#define drep(i,a,b) for(int i=a;i>=b;--i)
using namespace std;
struct edge{int x,y;
}e[mxn];
int n,m,c[mxn];
int tot,hd[mxn],vr[mxn<<1],ed[mxn<<1],nx[mxn<<1];
bool v[mxn],b[mxn];
vector<int>s1[mxn],s2[mxn];
inline void add(int x,int y,int z){vr[++tot]=y,ed[tot]=z,nx[tot]=hd[x],hd[x]=tot;
}
void dfs(int x,int fa){v[x]=1;for(int i=hd[x],y;i;i=nx[i])if(!v[y=vr[i]]){dfs(y,x);}if(c[x]<0||c[x]>2){puts("No");exit(0);}if(!c[x])return;if(!fa){puts("No");exit(0);}vector<int>s;for(int i=hd[x],y;i;i=nx[i])if(vr[i]==fa){s.pb(ed[i]);}if(c[x]>s.size()){puts("No");exit(0);}rept(i,0,c[x]){c[fa]--,b[s[i]]=1;}
}
inline void out(int x,int y){cout<<e[x].x<<" "<<e[x].y<<" "<<e[y].x<<" "<<e[y].y<<'\n';
}
signed main(){scanf("%d%d",&n,&m);for(int i=1,x,y;i<=m;++i){scanf("%d%d",&x,&y);e[i]={x,y},c[x]++;add(x,y,i),add(y,x,i);}rep(i,1,n)if(!v[i])dfs(i,0);rep(i,1,m){if(b[i])s2[e[i].y].pb(i);else s1[e[i].x].pb(i);}puts("Yes");rep(i,1,n){rept(j,0,s1[i].size())out(s2[i][j],s1[i][j]);}return 0;
}