LCII.GYM102082E Eulerian Flight Tour
(原题是PDF,没有题面的直接页面,就放一个vjudge的链接罢)
首先,当\(n\)是奇数时,完全图一定是欧拉图,故直接全连即可。
当\(n\)是奇数时,原图是欧拉图等价于补图上每个节点的度数都为奇。每个节点度数都为奇的充分必要条件是存在一座度数全为奇的生成森林。
这里有一种复杂度不太正确的解法:
考虑首先做一遍一般图匹配。接着,在原图中长度为\(2\)的路径连接着的所有点间做一般图匹配;然后是长度为\(3\)的路径……
这个算法的正确性显然——假如一条路径上的所有边都被选上了,只有两端的点的奇偶性改变了;而每个节点只会出现在一条路径的一端。为了避免两条路径有交,所以我们要按照长度处理,这样两条有交的路径就会被拆成两条无交的路径。
单次一般图匹配的复杂度是\(O\Big(n(n\log n+m)\Big)\)(带花树算法)(别问,问就是不会,从网上弄的板子);因为所有长度的路径数量之和是\(O(n^2)\)的,所以总复杂度是\(\sum n(n\log n+m)=n^3\log n+n^3=n^3\log n\)。
虽然理论复杂度能过,但实际上常数很大,最终T掉了。
代码(带花树部分来自网络,TLE):
#include<bits/stdc++.h>
using namespace std;
void read(int &x){
x=0;
char c=getchar();
while(c>'9'||c<'0')c=getchar();
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
}
const int N = 110;
int T,n,m,e,cnt,tot,ans,hd[N],p[N],match[N],pre[N],vst[N],dfn[N];
queue<int>q;
struct edge{int t,nxt;}es[N*N];
inline void Add(register int u,register int v){es[++tot]=(edge){v,hd[u]};hd[u]=tot;}
inline void add(register int u,register int v){Add(u,v),Add(v,u);}
int find(register int x){return x==p[x]?x:p[x]=find(p[x]);}
inline int lca(register int u,register int v){
for(++cnt,u=find(u),v=find(v);dfn[u]!=cnt;){
dfn[u]=cnt;
u=find(pre[match[u]]);
if(v)swap(u,v);
}
return u;
}
inline void blossom(register int x,register int y,register int w){
while(find(x)!=w){
pre[x]=y,y=match[x];
if(vst[y]==2)vst[y]=1,q.push(y);
if(find(x)==x)p[x]=w;
if(find(y)==y)p[y]=w;
x=pre[y];
}
}
inline int aug(register int s){
if((ans+1)*2>n)return 0;
for(register int i=1;i<=n;++i)p[i]=i,vst[i]=pre[i]=0;
while(!q.empty())q.pop();
for(q.push(s),vst[s]=1;!q.empty();q.pop())
for(register int u(q.front()),i(hd[u]),v,w;i;i=es[i].nxt){
if(find(u)==find(v=es[i].t)||vst[v]==2)continue;
if(!vst[v]){
vst[v]=2;pre[v]=u;
if(!match[v]){
for(register int x=v,lst;x;x=lst)lst=match[pre[x]],match[x]=pre[x],match[pre[x]]=x;
return 1;
}
vst[match[v]]=1,q.push(match[v]);
}else blossom(u,v,w=lca(u,v)),blossom(v,u,w);
}
return 0;
}
bool g[N][N];
bool has[N];
bool no[N][N];
int dis[N][N],TOT;
int main(){
read(n),read(m);
for (int i=1,x,y;i<=m;++i)read(x),read(y),g[x][y]=true;
TOT=n*(n-1)/2-m;
if(n&1){
printf("%d\n",TOT);
for(int i=1;i<=n;i++)for(int j=i+1;j<=n;j++)if(!g[i][j])printf("%d %d\n",i,j);
}else{
for(int i=1;i<=n;i++)for(int j=1;j<=n;j++){
if(!g[i][j])dis[i][j]=1;
else dis[i][j]=0x3f3f3f3f;
if(i==j)dis[i][j]=0;
}
for(int k=1;k<=n;k++)for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
for(int d=1;d<=n;d++){
memset(hd,0,sizeof(hd)),tot=0;
for(int i=1;i<=n;i++)for(int j=i+1;j<=n;j++)if(!has[i]&&!has[j]&&dis[i][j]==d)add(i,j);
for (int i=1;i<=n;++i) if (!match[i]) ans+=aug(i);
for (int i=1;i<=n;++i){
if(!match[i]||has[i])continue;
has[i]=true;
if(match[i]>i)continue;
int j=i;
while(j!=match[i])for(int k=1;k<=n;k++)if(!g[j][k]&&dis[match[i]][k]+1==dis[match[i]][j]){g[j][k]=g[k][j]=no[j][k]=no[k][j]=true,j=k,TOT--;break;}
}
if(ans*2==n){
for(int i=1;i<=n;i++){
bool ok=false;
for(int j=1;j<=n;j++)if(i!=j)ok|=!no[i][j];
if(!ok){puts("-1");return 0;}
}
printf("%d\n",TOT);
for(int i=1;i<=n;i++)for(int j=i+1;j<=n;j++)if(!g[i][j])printf("%d %d\n",i,j);
return 0;
}
}
puts("-1");
}
return 0;
}
下面是正解:
每个节点度数都为奇的充分必要条件还有一个,就是不存在点数为奇的连通块。假如该条件成立,我们考虑构造一组解。我们考虑对于每个连通块都求一棵生成树,然后断掉某些边使得每个节点度数都为奇。这个可以从叶子向上DP,如果一个节点的儿子中有偶数条边保留了,则其与父亲的边也需保留,否则则需断开。
需要注意的是,原图必须是连通图,等价于补图中不能有度数为\(n-1\)的节点。假如我们发现存在这样的节点,则如果当前生成树是唯一生成树,无解;否则,考虑找到另一棵生成树。这可以通过找到一条原树中没有的边,然后强制连上它,然后重新求生成树得到。
代码:
#include<bits/stdc++.h>
using namespace std;
void read(int &x){
x=0;
char c=getchar();
while(c>'9'||c<'0')c=getchar();
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
}
int n,m;
const int N=110;
vector<int>v[N];
int dsu[N],sz[N],deg[N];
int find(int x){return dsu[x]==x?x:dsu[x]=find(dsu[x]);}
bool merge(int x,int y){
int p=find(x),q=find(y);
if(p==q)return true;
v[y].push_back(x),v[x].push_back(y),deg[y]++,deg[x]++,dsu[p]=q,sz[q]+=sz[p];
return false;
}
bool g[N][N];
bool no[N][N];
int TOT;
bool dfs(int x,int fa){
bool ok=true;
for(auto y:v[x]){
if(y==fa)continue;
if(dfs(y,x))no[y][x]=no[x][y]=true,ok^=1,TOT--;
}
return ok;
}
int main(){
read(n),read(m);
for (int i=1,x,y;i<=m;++i)read(x),read(y),g[x][y]=true;
TOT=n*(n-1)/2-m;
if(n&1){
printf("%d\n",TOT);
for(int i=1;i<=n;i++)for(int j=i+1;j<=n;j++)if(!g[i][j])printf("%d %d\n",i,j);
}else{
for(int i=1;i<=n;i++)dsu[i]=i,sz[i]=1;
bool ok=false;
for(int i=n;i;i--)for(int j=n;j>i;j--)if(!g[i][j])ok|=merge(i,j);
for(int i=1;i<=n;i++)if(dsu[i]==i&&(sz[i]&1)){puts("-1");return 0;}
int invalid=0;
for(int i=1;i<=n;i++)if(deg[i]==n-1){
if(!ok){puts("-1");return 0;}
invalid=i;
}
if(invalid){
for(int i=1;i<=n;i++)dsu[i]=i,sz[i]=1,deg[i]=0,v[i].clear();
int x=0,y=0;
for(int i=n;i;i--)for(int j=n;j>i;j--){
if(g[i][j])continue;
if(merge(i,j)&&!x&&!y)x=i,y=j;
}
for(int i=1;i<=n;i++)dsu[i]=i,sz[i]=1,deg[i]=0,v[i].clear();
merge(x,y);
for(int i=n;i;i--)for(int j=n;j>i;j--)if(!g[i][j])merge(i,j);
}
for(int i=1;i<=n;i++)if(dsu[i]==i)dfs(i,0);
printf("%d\n",TOT);
for(int i=1;i<=n;i++)for(int j=i+1;j<=n;j++)if(!g[i][j]&&!no[i][j])printf("%d %d\n",i,j);
}
return 0;
}