POJ-1637 混合图欧拉回路-网络流求解
题意:给定一个混合图,所谓混合图就是图中既有单向边也有双向边,现在求这样的图是否存在欧拉回路。
解法:首先回顾下有向图的欧拉回路的条件是什么:所有的节点出度等于入度。现考虑到下面的这个图的转化过程:
假设上方图中的A,B两点代表在有向图存在欧拉回路时任意两点之间连边关系,显然有两个点的入度等于出度。现在考虑改变某条边的关系,那么可以看到新的入度和出入如下图,这一有一个不变量就是任意一个点的入度和出度之差的奇偶性不变。
现列步骤如下:
1.为任意一条无向边选择一个方向。如果存在欧拉回路的话,那么可以得到每个点入度于出度差至少应满足为偶数。否则一定不能够构成欧拉回路。
2.在满足上述条件的基础上,我们需要对我们假定的边进行修正,如果最后能够修正成所有点的入度等于出度那么就能够得出这个混合图的欧拉回路(通过正确的化无向边为有向边)。如何修正呢,这时就可以转化为网络流问题求解:
可以定义对于入度大于出度的节点需要流出一些流量,出度大于入度的点需要接收一些流量,然后建立超级源点和超级汇点保持流平衡即可。具体这样构边:
A.某节点 i 入度大于出度,就从源点S连一条流量为(in[i] - out[i]) / 2的边到 i 。
B.某节点 i 出度大于入度,就从 i 连一条流量为(out[i] - in[i]) / 2的边到汇点T。
C.遍历 <i, j> 若假定 i, j 之间连接了mp[i][j] 条无向边,那么连接一条从 j 到 i 流量为mp[i][j]的边。为什么反过来,因为这是我们假设的,意味着我们有多少反悔的资本。
代码如下:
#include <cstdlib> #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; const int SS = 202, TT = 203; const int INF = 0x3fffffff; int N, M, flow; int in[205], out[205]; int mp[205][205]; // 用来记录无向边 struct Edge { int v, c, next; }; Edge e[10000]; int idx, head[205]; int lv[205], que[205]; int front, tail; void insert(int a, int b, int c) { e[idx].v = b, e[idx].c = c; e[idx].next = head[a]; head[a] = idx++; } bool Euler() { flow = 0; for (int i = 1; i <= N; ++i) { if (in[i] > out[i]) flow += (in[i]-out[i]) >> 1; // 定义入度大于出度的点流出流量 if (abs(in[i]-out[i]) & 1) { // 如果入度和出度之差为奇数 return false; } } return true; } bool bfs() { front = tail = 0; memset(lv, 0xff, sizeof (lv)); lv[SS] = 0; que[tail++] = SS; while (front < tail) { int u = que[front++]; for (int i = head[u]; i != -1; i = e[i].next) { int v = e[i].v; if (!(~lv[v]) && e[i].c) { lv[v] = lv[u] + 1; if (v == TT) return true; que[tail++] = v; } } } return false; } int dfs(int u, int sup) { if (u == TT) return sup; int tf = 0, f; for (int i = head[u]; i != -1; i = e[i].next) { int v = e[i].v; if (lv[u]+1 == lv[v] && e[i].c && (f=dfs(v, min(e[i].c, sup-tf)))) { tf += f; e[i].c -= f, e[i^1].c += f; if (tf == sup) return sup; } } if (!tf) lv[u] = -1; return tf; } int dinic() { int ret = 0; while (bfs()) { ret += dfs(SS, INF); } return ret; } void solve() { idx = 0; memset(head, 0xff, sizeof (head)); for (int i = 1; i <= N; ++i) { if (in[i] > out[i]) { insert(SS, i, (in[i]-out[i])>>1); insert(i, SS, 0); } else if (out[i] > in[i]) { insert(i, TT, (out[i]-in[i])>>1); insert(TT, i, 0); } for (int j = 1; j <= N; ++j) { if (mp[i][j]) {// 这是假定的双向边的方向 insert(j, i, mp[i][j]); insert(i, j, 0); } } } if (dinic() == flow) { puts("possible"); } else { puts("impossible"); } } int main() { int T, a, b, c; scanf("%d", &T); while (T--) { scanf("%d %d", &N, &M); // N个点,M条边 memset(in, 0, sizeof (in)); memset(out, 0, sizeof (out)); memset(mp, 0, sizeof (mp)); for (int i = 0; i < M; ++i) { scanf("%d %d %d", &a, &b, &c); ++in[b], ++out[a]; if (!c) {// 说明该边是双向的 ++mp[a][b]; // 默认把a作为弧尾,b作为弧头 } } if (!Euler()) { puts("impossible"); continue; } solve(); } return 0; }