嗯,这是我上一篇文章说的那本宝典的第二题,我只想说,真TM是本宝典……做的我又痛苦又激动……(我感觉ACM的日常尽在这张表情中了)
题目链接:http://poj.org/problem?id=1637
Time Limit: 1000MS Memory Limit: 10000K
Description
Input
Output
Sample Input
4
5 8
2 1 0
1 3 0
4 1 1
1 5 0
5 4 1
3 4 0
4 2 1
2 2 0
4 4
1 2 1
2 3 0
3 4 0
1 4 1
3 3
1 2 0
2 3 0
3 2 0
3 4
1 2 0
2 3 1
1 2 0
3 2 0
Sample Output
possible
impossible
impossible
possible
然后我看[网络流建模汇总][Edelweiss].pdf上面写的跟啥XX玩意儿一样,就网上去查这道题的题解……然后最后还是要紧紧围绕在kuangbin大佬周围,实现共产主义……
题意:
一幅图 ,给出有向边和无向边,问是否有经过所有边仅一次的欧拉回路。
嗯,具体的,它先给你了个case数,然后m个点,s条边,后面跟着s条边的from,to,type,type==0时是双向边,type==1时是单向边,让你回答欧拉回路im不impossible……
解题思路:
判断欧拉路是否存在的方法:
有向图:图连通,有一个顶点出度大入度1,有一个顶点入度大出度1,其余都是出度=入度。
无向图:图连通,只有两个顶点是奇数度,其余都是偶数度的。
判断欧拉回路是否存在的方法:
有向图:图连通,所有的顶点出度=入度。
无向图:图连通,所有顶点都是偶数度。
混合图欧拉回路的求解需要用到网络流,具体的建模方法如下:
1、先给所有无向边定向,然后统计所有点的入度和出度,
2、如果某点 入度-出度=奇数 那么一定不能构成欧拉回路,可以直接得到答案,否则就继续下面的步骤——
3、如果某点 出度>入度 建一条与源点连接的边 边容量为 (出度-入度)/2;
如果某点 出度<入度 建一条与汇点连接的边 边容量为 (入度-出度)/2;
如果某点 出度==入度 这个点就不要放到流网络图里了。
4、所有无向边按已定方向建边,边容量为1
5、跑最大流,如果满流 ,那么就能构成欧拉回路。
当然啦,详细的,请参见http://www.cnblogs.com/kuangbin/p/3537525.html:
【混合图】
混合图(既有有向边又有无向边的图)中欧拉环、欧拉路径的判定需要借助网络流! (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]<的点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]<,则i作起点j作终点(否则若D[i]与D[j]同号则不存在欧拉路径),连边<j, i>,求是否存在欧拉环即可(将求出的欧拉环中删去边<j, i>即可)。这样只需求一次最大流。 就是转化成最大流,最一次最大流,看是不是满流
最后的代码感觉反而是最简单的……贴个模板,然后格叽格叽……在WA了N发以后……就A了
#include<cstdio>
#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
#define MAXM 205
#define MAXS 1005
#define INF 0x3f3f3f3f
using namespace std;
int m,s;//m个点,s条边
int outd[MAXM],ind[MAXM];//出度、入度
struct Edge{
int u,v,c,f;
};
struct Dinic
{
int s,t;
vector<Edge> E;
vector<int> G[MAXM];
bool vis[MAXM]; //BFS使用
int lev[MAXM];//记录层次
int cur[MAXM]; //当前弧下标
void init(int n)
{
E.clear();
for(int i=;i<=n;i++) G[i].clear();
}
void addedge(int from,int to,int cap)
{
E.push_back((Edge){from,to,cap,});
E.push_back((Edge){to,from,,});
int m=E.size();
G[from].push_back(m-);
G[to].push_back(m-);
}
bool bfs()
{
memset(vis,,sizeof(vis));
queue<int> q;
q.push(s);
lev[s]=;
vis[s]=;
while(!q.empty())
{
int now=q.front(); q.pop();
for(int i=;i<G[now].size();i++)
{
Edge edge=E[G[now][i]];
int nex=edge.v;
if(!vis[nex] && edge.c>edge.f)//属于残存网络的边
{
lev[nex]=lev[now]+;
q.push(nex);
vis[nex]=;
}
}
}
return vis[t];
}
int dfs(int now,int aug)//now表示当前结点,aug表示目前为止的最小残量
{
if(now==t || aug==) return aug;//aug等于0时及时退出,此时相当于断路了
int flow=,f;
for(int& i=cur[now];i<G[now].size();i++)//从上次考虑的弧开始,注意要使用引用,同时修改cur[now]
{
Edge& edge=E[G[now][i]];
int nex=edge.v;
if(lev[now]+ == lev[nex] && (f=dfs(nex,min(aug,edge.c-edge.f)))>)
{
edge.f+=f;
E[G[now][i]^].f-=f;
flow+=f;
aug-=f;
if(!aug) break;//aug等于0及时退出,当aug!=0,说明当前节点还存在另一个增广路分支
}
}
return flow;
}
int maxflow()//主过程
{
int flow=;
while(bfs())//不停地用bfs构造分层网络,然后用dfs沿着阻塞流增广
{
memset(cur,,sizeof(cur));
flow+=dfs(s,INF);
}
return flow;
}
}dinic;
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&m,&s); dinic.init(m+);
dinic.s=, dinic.t=m+;
for(int i=;i<=m;i++) outd[i]=ind[i]=;//初始化一波 for(int i=;i<=s;i++)
{
int from,to,type;
scanf("%d%d%d",&from,&to,&type);
if(from==to) continue;//这种蜜汁弧就不要了
outd[from]++;
ind[to]++;
if(!type) dinic.addedge(from,to,);//双向边扔流网络里
} bool ok1=,ok2=;
for(int i=,d;i<=m;i++)
{
d=outd[i]-ind[i];
if(d%)
{
ok1=;
break;
}
if(d!=)
{
ok2=;
if(d>) dinic.addedge(dinic.s,i,d/);//和源点相连,扔流网络里
else dinic.addedge(i,dinic.t,-d/);//和汇点相连,扔流网络里
}
}
if(!ok1)//有一个d[i]是奇数:impossible
{
printf("impossible\n");
continue;
}
if(ok2)//所有的d[i]都等于0:possible
{
printf("possible\n");
continue;
} int s_outd=;//源点的所有出弧的cap和
for(int i=,_size=dinic.G[dinic.s].size();i<_size;i++)
{
Edge e=dinic.E[(dinic.G[dinic.s][i])];
s_outd+=e.c;
} if(s_outd==dinic.maxflow()) printf("possible\n");
else printf("impossible\n");
}
}