这是一篇被 hb 强迫写的题解(
首先一个图是欧拉图当且仅当它连通且每个点度数为偶数。
那么若 \(n\) 是奇数,则显然可以直接填成完全图,这样每个点的度数是 \(n-1\)(偶数)且连通,符合要求。接下来讨论 \(n\) 是偶数的情况。
考虑按连通性分类。
-
连通。此时只要添边满足每个点度数为偶数即可,因为无论怎么添边它永远是连通的。注意到若在给定图的基础上添边,这样每个点的初始度数是参差不齐的;不妨转化为在完全图的基础上删边使得每个点的度数都为偶数,这样每个点的初始度数都是奇数。删的边有限制,必须在给定图的补图中,因为不能破坏本来就有的边。
我们考虑将补图建出来。显然每个连通分量是独立的,考虑一个连通分量内是否能删边使得每个点度数都为偶数。一个显然的必要条件是此连通分量点数为偶,否则无论如何删,度数为奇的点的个数永远是奇数,不会变成 \(0\)。然后可以证明这个必要条件也充分。考虑这个连通分量的任意一棵生成树,对它归纳证:每个子树内,如果点数为奇则一定可以使得非根节点都度数为偶而根为奇,点数为偶则可以使得所有节点度数都为偶。
- 叶子节点显然满足;
- 假设当前节点的儿子都满足。若当前节点子树内节点数量为奇,则容易得到儿子子树内点数为奇的有偶数个,将当前节点连向它们的边都删掉,它们度数都偶了,留下当前节点依然度数为奇;若为偶,易得儿子子树内点数为奇的有奇数个,同样将当前节点连向它们的边都删掉,这样它们和当前节点的度数都偶了。
得证。至于构造方案,则可类似上面证明过程 DP 一下即可,\(dp_i\) 表示子树 \(i\) 内是留下根独奇,还是全偶;
-
不连通。此时虽然不能无顾虑地只考虑将每个点的度数弄成偶数,但是不连通图有个特性:补图一定连通,而且对于原图任意两个连通分量,其一中的每个点到其二中的每个点在补图中都有边。考虑利用这个特性,依然在补图中删边,在让每个点的度数变成偶数的基础上还要让删剩下的边把原图连连通。
考虑将原图的连通分量们分成两组,显然两组内其一中的每个点到其二中的每个点在补图中都有边。设第一组节点为 \(a_{1\sim m}\),第二组为 \(b_{1\sim s}\),设 \(m\leq s\)。则对于 \(i\in[1,m]\),考虑将 \(a_i,b_i\) 配对(即删掉补图中 \(a_i,b_i\) 的边)。此时只有剩下来的 \(b_{m+1\sim s}\) 的度数还是奇数。由 \(n\) 是偶数可以得到 \(s-(m+1)+1\) 是偶数,考虑将 \(b_{m+1\sim s}\) 都与 \(a_m\) 配对,这样全图度数偶性已解决。接下来考虑连通性。
显然这样配对的话,两组内部一定是连通的,因为其内部在补图中的边一个也没删。
若 \(m>1\),则 \(a_1\) 在原图中只把和 \(b_1\) 的边删掉了,留下了与 \(b_{2\sim s}\) 的边,这已足以让两组连通。于是这样配对是保证连通性的。
然后不能找到分配方案使得 \(m>1\) 当且仅当只有两个连通分量,且其中一个大小为 \(1\)。这时可分两种情况:
- \(b_{1\sim s}\) 这个连通分量是完全图。这样没救了,输出
-1
; - \(b_{1\sim s}\) 这个连通分量不是完全图。则可以在上述分配方案的基础上,找到任意一组 \(b_i,b_j\) 在补图中有边,少删 \((a_1,b_i),(a_1,b_j)\) 两条边,多删 \((b_i,b_j)\) 一条边,则是合法方案。
- \(b_{1\sim s}\) 这个连通分量是完全图。这样没救了,输出
#include<bits/stdc++.h>
using namespace std;
#define pb push_back
#define mp make_pair
#define X first
#define Y second
const int N=100;
int n,m;
bool con[N+1][N+1];
bool dp[N+1];
bool vis[N+1];
vector<int> v;
void dfs0(int x){
vis[x]=true;
v.pb(x);
for(int i=1;i<=n;i++)if(con[i][x]&&!vis[i])dfs0(i);
}
bool in[N+1];
void dfs(int x){
vis[x]=true;
dp[x]=true;
for(int i=1;i<=n;i++)if(!con[i][x]&&!vis[i]){
dfs(i);
if(dp[i])con[x][i]=con[i][x]=true,dp[i]=false,dp[x]^=1;
}
}
int main(){
cin>>n>>m;
for(int i=1;i<=m;i++){
int x,y;
cin>>x>>y;
con[x][y]=con[y][x]=true;
}
if(!(n&1)){
dfs0(1);
bool flg=true;
for(int i=1;i<=n;i++)flg&=vis[i];
memset(vis,0,sizeof(vis));
if(!flg){
vector<vector<int> > vv;
for(int i=1;i<=n;i++)if(!vis[i]){
v.clear();
dfs0(i);
vv.pb(v);
}
flg=false;
for(int i=0;i<vv.size();i++)if(vv[i].size()>1){
for(int j=0;j<vv[i].size();j++)in[vv[i][j]]=true;
flg=true;
break;
}
if(!flg){
if(vv.size()==2)return puts("-1"),0;
else in[vv[0][0]]=in[vv[1][0]]=true;
}
vector<int> in1,in2;
for(int j=1;j<=n;j++)if(in[j])in1.pb(j);else in2.pb(j);
if(in1.size()>in2.size())swap(in1,in2);
if(in1.size()==1){
flg=true;
for(int j=1;j<=n;j++)for(int k=j+1;k<=n;k++)if(j!=in1[0]&&k!=in1[0])flg&=con[j][k];
if(flg)return puts("-1"),0;
for(int j=1;j<=n;j++){
bool ext=false;
for(int k=j+1;k<=n;k++)if(j!=in1[0]&&k!=in1[0]&&!con[j][k]){
for(int o=1;o<=n;o++)if(o!=in1[0]&&o!=j&&o!=k)con[in1[0]][o]=con[o][in1[0]]=true;
con[j][k]=con[k][j]=true;
ext=true;break;
}
if(ext)break;
}
}
else{
for(int j=0;j<in2.size();j++)
con[in1[min(int(in1.size())-1,j)]][in2[j]]=con[in2[j]][in1[min(int(in1.size())-1,j)]]=true;
}
}
else{
for(int i=1;i<=n;i++)if(!vis[i]){
dfs(i);
if(dp[i])return puts("-1"),0;
}
}
}
vector<pair<int,int> > ans;
for(int i=1;i<=n;i++)for(int j=i+1;j<=n;j++)if(!con[i][j])ans.pb(mp(i,j));
cout<<ans.size()<<"\n";
for(int i=0;i<ans.size();i++)cout<<ans[i].X<<" "<<ans[i].Y<<"\n";
return 0;
}