题目链接
题意
给定一个混合图,里面既有有向边也有无向边。问该图中是否存在一条路径,经过每条边恰好一次。
思路
从欧拉回路说起
首先回顾有向图欧拉回路的充要条件:(forall vin G, d_{in}(v)=d_{out}(v)).
现在这个图中有一些无向边,那怎么办?
那就转化成有向边呀。
对无向边随意定向,得到一个有向图。在这个有向图中,如果有(forall vin G, abs(d_{in}(v)-d_{out}(v)))为偶数,则将其中一些边反向,肯定能得到一个欧拉图。而若为奇数,则肯定不可以。
为什么?可以有两种考虑方式:
1.将所有的边看成无向边,那么,“对于每个点来说入度与出度之差绝对值为偶数”,这个条件就意味着,“对于每个点其度(=入度+出度)为偶数”,而这正是无向图欧拉回路的充要条件。现在我们得到了这个无向图中的欧拉回路,一路走一路定向,就得到了有向图中的欧拉回路。对比原有向图中的边的方向与现在得到的欧拉回路中的边的方向,将其中一些反向即可。
2. 直接从修改的角度想,将一条边反向的效果是,它的两个端点的入度与出度之差都变化2,最终可以使得达到每个点的入度与出度之差为0的效果,即得到欧拉回路。
(第二种说得不太严谨...)
问题转化
但是这道题是不是这么简单的呢?并不是。
为什么?因为我们并不能将其中任意一条边随意反向,不然这就跟给了一张无向图没什么差别了。
所以,限制就在于:将限定范围内的一些边反向,问能否得到一个欧拉图。
于是问题转化为,现有一些点,其中一些入度(gt)出度,另一些出度(gt)入度。将其中一些边反向,问能否满足所有的点的入度(==)出度。
建图
由上面的关系可以很容易联想到网络流。
因为有向边是不可以反向的,所以对度数的改变没有任何贡献,不加入图中。
而经过定向成为有向边的边(e=(u,v))在这一模型中的贡献是通过其反向可使(u)的入度-出度值增加(2),(v)的出度-入度值增加(2),故将之加入图中,权值为(2),意为将这一条边反向对度数的改变贡献为(2)。
接下来的步骤就很顺理成章了:
- 在 源点 到 出度(gt)入度的点 之间加边,边权为出度与入度之差
- 在 入度(gt)出度的点 到 汇点 之间加边,边权为入度与出度之差
最后只需判断最大流是否为满流即可。
Code
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <iostream>
#define maxn 1010
#define maxm 10010
#define inf 0x3f3f3f3f
using namespace std;
typedef long long LL;
struct Edge { int to, ne, c; }edge[maxm];
int dep[maxn], ne[maxn], n, m, tot, s,t, out[maxn], in[maxn];
void add(int u, int v, int c) {
edge[tot] = {v, ne[u], c};
ne[u] = tot++;
edge[tot] = {u, ne[v], 0};
ne[v] = tot++;
}
int bfs(int src) {
memset(dep, 0, sizeof dep);
dep[src] = 1;
queue<int> q;
while (!q.empty()) q.pop();
q.push(src);
while (!q.empty()) {
int u = q.front(); q.pop();
for (int i = ne[u]; ~i; i = edge[i].ne) {
int v = edge[i].to;
if (edge[i].c > 0 && !dep[v]) dep[v] = dep[u] + 1, q.push(v);
}
}
return dep[t];
}
int dfs(int u, int flow) {
if (u == t) return flow;
int ret = 0;
for (int i = ne[u]; ~i; i = edge[i].ne) {
int v = edge[i].to;
if (edge[i].c > 0 && dep[v] == dep[u] + 1) {
int c = dfs(v, min(flow-ret, edge[i].c));
edge[i].c -= c;
edge[i^1].c += c;
ret += c;
if (ret == flow) break;
}
}
if (!flow) dep[u] = 0;
return ret;
}
void work() {
scanf("%d%d", &n, &m);
tot = 0; memset(ne, -1, sizeof ne);
memset(out, 0, sizeof out); memset(in, 0, sizeof in);
s = 0, t = n+1;
for (int i = 0; i < m; ++i) {
int u, v, t;
scanf("%d%d%d", &u, &v, &t);
if (!t) add(u, v, 2);
++out[u], ++in[v];
}
int cnt=0;
for (int i = 1; i <= n; ++i) {
if (abs(out[i]-in[i])&1) { puts("impossible"); return; }
if (out[i] > in[i]) add(s, i, out[i]-in[i]), cnt += out[i]-in[i];
else if (out[i] < in[i]) add(i, t, in[i]-out[i]);
}
int ret=0,ans=0;
while (bfs(s)) {
while (ret = dfs(s, inf)) ans += ret;
}
if (ans == cnt) puts("possible");
else puts("impossible");
}
int main() {
int T;
scanf("%d", &T);
while (T--) work();
return 0;
}