很明显是一道混合图(既有有向边又有无向边的图)求欧拉路径(或回路)的题,接下来介绍这道题的模板流程。
流程原理概述:
混合图(既有有向边又有无向边的图)中欧拉环、欧拉路径的判定需要借助网络流!
(1)欧拉环的判定:
一开始当然是判断原图的基图是否连通,若不连通则一定不存在欧拉环或欧拉路径(不考虑度数为0的点)。
其实,难点在于图中的无向边,需要对所有的无向边定向(指定一个方向,使之变为有向边),使整个图变成一个有向欧拉图(或有向半欧拉图)。若存在一个定向满足此条件,则原图是欧拉图(或半欧拉图)否则不是。关键就是如何定向?
首先给原图中的每条无向边随便指定一个方向(称为初始定向),将原图改为有向图G',然后的任务就是改变G'中某些边的方向(当然是无向边转化来的,原混合图中的有向边不能动)使其满足每个点的入度等于出度。
设D[i]为G'中(点i的出度 - 点i的入度)。可以发现,在改变G'中边的方向的过程中,任何点的D值的奇偶性都不会发生改变(设将边<i, j>改为<j, i>,则i入度加1出度减1,j入度减1出度加1,两者之差加2或减2,奇偶性不变)!而最终要求的是每个点的入度等于出度,即每个点的D值都为0,是偶数,故可得:若初始定向得到的G'中任意一个点的D值是奇数,那么原图中一定不存在欧拉环!
若初始D值都是偶数,则将G'改装成网络:设立源点S和汇点T,对于每个D[i]>0的点i,连边<S, i>,容量为D[i]/2;对于每个D[j]<0的点j,连边<j, T>,容量为-D[j]/2;G'中的每条边在网络中仍保留,容量为1(表示该边最多只能被改变方向一次)。求这个网络的最大流,若S引出的所有边均满流,则原混合图是欧拉图,将网络中所有流量为1的中间边(就是不与S或T关联的边)在G'中改变方向,形成的新图G''一定是有向欧拉图;若S引出的边中有的没有满流,则原混合图不是欧拉图。
为什么能这样建图?
考虑网络中的一条增广路径S-->i-->...-->j-->T,将这条从i到j的路径在G'中全部反向,则:i的入度加1出度减1,j的入度减1出度加1,路径中其它点的入度出度均不变。而i是和S相连的,因此初始D[i]>0,即i的出度大于入度,故这样反向之后D[i]减少2;同理,j是和T相连的,这样反向之后D[j]增加2。因此,若最大流中边<S, i>满流(流量为初始D[i]/2),此时D[i]值就变成了0,也就是i的入度等于出度。因此只要使所有S引出的边全部满流,所有初始D值>0的点的D值将等于0,又因为将边变向后所有点的D值之和不变,所有初始D值小于0的点的D值也将等于0,而初始D值等于0的D点既不与S相连也不与T相连,所以它们是网络中的中间点,而中间点的流入量等于流出量,故它们的入度和出度一直不变,即D值一直为0。因此,整个图G'成为欧拉图。
(2)欧拉路径的判定:
首先可以想到的是枚举欧拉路径的起点i和终点j,然后在图中添加边<j, i>,再求图中是否有欧拉回路即可。但是,该算法的时间复杂度达到了O(M * 最大流的时间),需要优化。
前面已经说过,在将边变向的过程中任何点的D值的奇偶性都不会改变,而一个有向图有欧拉路径的充要条件是基图连通且有且只有一个点的入度比出度少1(作为欧拉路径的起点),有且只有一个点的入度比出度多1(作为终点),其余点的入度等于出度。这就说明,先把图中的无向边随便定向,然后求每个点的D值,若有且只有两个点的初始D值为奇数,其余的点初始D值都为偶数,则有可能存在欧拉路径(否则不可能存在)。进一步,检查这两个初始D值为奇数的点,设为点i和点j,若有D[i]>0且D[j]<0,则i作起点j作终点(否则若D[i]与D[j]同号则不存在欧拉路径),连边<j, i>,求是否存在欧拉环即可(将求出的欧拉环中删去边<j, i>即可)。这样只需求一次最大流。
注意易错点(记住):
①双向道路是无向边,单向道路是有向边,我们需要考虑有向边和无向边(无向边随意设定方向来计算点的出入度即可),记录出每个点的出度和入度。但是我们建图只需要无向边(在图中互相建立容量为1的边),注意有向边不需要加到图里。
②记住建图模型(主要是这张图!):
代码(由于未解决输出道路重复的问题,未满分):
#include<iostream>
#include<bits/stdc++.h>
using namespace std;
const int maxn=210;
const int maxm=1010;
struct edge
{
int v,c,next;
}e[maxn];
int p[maxm];
void init()
{
memset(p,-1,sizeof(p));
}
int cnt=0;
void insert1(int u,int v,int c)
{
e[++cnt].v=v;
e[cnt].c=c;
e[cnt].next=p[u];
p[u]=cnt;
}
int d[maxn];
int n;
bool bfs()
{
memset(d,0,sizeof(d));
d[0]=1;
queue<int> q;
q.push(0);
while(q.empty()==false)
{
int a=q.front();
q.pop();
for(int i=p[a];i!=-1;i=e[i].next)
{
int ee=e[i].v;
if(d[ee]==0 && e[i].c>0)
{
d[ee]=d[a]+1;
q.push(ee);
}
}
}
return (d[n+1]!=0);
}
int dfs(int s,int flow)
{
if(s==n+1)
return flow;
int ans=0;
for(int i=p[s];i!=-1;i=e[i].next)
{
int ee=e[i].v;
if(d[ee]==d[s]+1 && e[i].c>0)
{
int temp=dfs(ee,min(flow,e[i].c));
flow-=temp;
ans+=temp;
e[i].c-=temp;
e[i^1].c+=temp;
if(flow==0)
break;
}
}
if(ans==0)
d[s]=0;
return ans;
}
int chu[maxn];
int ru[maxn];
int main()
{
init();
int m;
cin>>n>>m;
for(int i=1;i<=m;i++)
{
int x,y,dd;
scanf("%d%d%d",&x,&y,&dd);
if(dd==0) //双向街道——无向边:随意设一个方向 作为单向边
{
insert1(x,y,1);
insert1(y,x,1);
chu[x]++;
ru[y]++;
}
else //单向街道——有向边,不用加入图
{
chu[x]++;
ru[y]++;
}
}
//寻找欧拉路径中的起点和终点,而其他点都须满足D为偶数
int countt=0;
int qi=0,zhong=0;
for(int i=1;i<=n;i++)
{
int D=abs(chu[i]-ru[i]);
if(D%2!=0)
{
countt++;
if(D>0)
qi=i;
else
zhong=i;
}
}
if(countt>2) //只能有一个起点一个终点 共两点
{
cout<<"impossible";
return 0;
}
//接下来开始照模板建图 (无向边已经在之前建好!)
int sum=0; //记录与源点相连的满流
for(int i=1;i<=n;i++)
{
int DD=(chu[i]-ru[i])/2;
if(DD>0)
{
insert1(0,i,DD);
insert1(i,0,0);
sum+=DD;
}
else
{
insert1(i,n+1,-DD);
insert1(n+1,i,0);
}
}
//记得要把终点连向起点,这样就可以直接找欧拉回路了
if(zhong!=0 && qi!=0)
{
insert1(zhong,qi,1);
insert1(qi,zhong,0);
}
int ans=0;
while(bfs())
{
ans+=dfs(0,0x3f3f3f3f);
}
if(ans==sum)
cout<<"possible";
else
cout<<"impossible";
return 0;
}