Problem
给定一张n(≤105)n(≤105)个点、m(≤105)m(≤105)条边的无向图(可能存在自环和重边,图可能不连通)。小C可任选起点开始,进行以下操作的一个:
- 0 v,选择一条相邻的还未经过的边走到v
1 v,传送到点v
小C要走一个欧拉路径。求最小传送次数,并给出一种方案。
Solution
- 先考虑如何求答案(最少添加多少条边)。
- 注意到图可能不连通,那么我们肯定是跑完一个联通块内的,再去其他的联通块。因此,每个联通块的答案应是相对独立的。
- 设有k个联通块,第i个中有ci个奇点,则答案为∑ki=1max(1,c2)−1∑i=1kmax(1,c2)−1。
- 因为根据无向图中欧拉路径的判定条件:奇点数为0或2,并且这两个奇点其中一个为起点另外一个为终点。我们每次传送到一个奇点,然后通过一条欧拉路径走到另一个奇点;然后再传送到一个奇点,再走到另一个奇点……如此往复。后面-1是因为选起点不计入传送次数。
- 知道了原因后,构造方案也非常简单了。
- 对于每一个联通块,我们求一波它里面所有的奇点,如果没有奇点,则表明一定存在欧拉回路,直接跑圈套圈算法。
- 如果有奇点呢?
- 囿于一个图中所有点的度数和肯定是偶数,所以定然是存在偶数个奇点。我们新建一个超级源S,从S连向所有奇点,这样所有奇点度数+1变成偶点,S也是偶点,于是图中便不存在奇点了,那么再跑圈套圈算法。
- 至此,我们知道了跑这个联通块的顺序。那我讲一下方案构造。
- 如果是从S跑到点x,那x肯定是奇点,于是操作是1 x。
- 如果从某个点跑到S,那肯定是从奇点跑到S,我们就不用管(因为S是虚点)。
- 如果从某个点x跑到点y,那么操作是0 y。
- 时间复杂度:O(n+m)O(n+m)。
Code
#include <bits/stdc++.h>
#define fi first
#define se second
#define mp make_pair
#define fo(i,a,b) for(i=a;i<=b;i++)
#define re(i,x) for(int i=x; i; i=ne[i])
using namespace std;
typedef pair<int,int> P;
const int N=11e4,M=N<<3;
int i,j,n,m,x,y,tot=1,to[M],ne[M],fin[N],deg[N],d[M],top,sta[M],ans,q;
bool vis[N],bz[N],went[M];
P o[M];
void link(int x,int y)
{
to[++tot]=y; ne[tot]=fin[x]; fin[x]=tot; deg[x]++;
to[++tot]=x; ne[tot]=fin[y]; fin[y]=tot; deg[y]++;
}
void fill(int x)
{
vis[x]=1;
if(deg[x]&1) sta[++top]=x;
re(i,fin[x]) if(!vis[to[i]])fill(to[i]);
}
void Euler(int x)
{
d[d[0]=1]=x;
while(d[0])
{
rep:x=d[d[0]];
if(!bz[x]) bz[x]=1;
for(j=fin[x]; j; j=fin[x])
{
fin[x]=ne[j];
if(!went[j])
{
went[j]=went[j^1]=1; d[++d[0]]=to[j];
goto rep;
}
}
sta[++top]=x;
d[0]--;
}
}
int main()
{
freopen("miner.in","r",stdin);
freopen("miner.out","w",stdout);
scanf("%d%d",&n,&m);
fo(i,1,m)
{
scanf("%d%d",&x,&y);
link(x,y);
}
fo(i,1,n)
if(deg[i]&&!vis[i])
{
top=0;
fill(i);
fin[0]=0;
if(!top)
link(0,i), link(i,0);
else while(top)link(0,sta[top--]);
Euler(bz[0]=0);
while(top>1)
{
x=sta[top--];
if(x)
o[++q]=mp(0,x);
else o[++q]=mp(1,sta[top--]), ans++;
}
}
printf("%d\n%d\n",ans-1,o[1].se);
fo(i,2,q) printf("%d %d\n",o[i].fi,o[i].se);
}