混合图欧拉回路(核心也就是网络流啦)
于2023/2/22日的模拟赛遇到了这一东西。也是网络流应用的一种新模型,感觉是大有可为啊,写个博客记录下。
给定一个图,里面的边有的是有向边,有的是无向边,要求给出无向边的定向方案,使得图中有欧拉回路。如果无解,则输出 \(-1\)。
核心思想就是先随便给无向边定个向,再利用网络流的调整功能来调节边的方向,使得图中有欧拉回路。
步骤:
- 给所有无向边随便定一个方向,然后计算每个点的出度和入度,建立一个超级源点和超级汇点。
- 如果一个点的入度大于出度,那么就给它向超级汇点连一条最大流量为 \(\frac{入度-出度}{2}\) 的边。如果一个点的出度大于入度,就从超级源点向它连一条 \(\frac{出度 - 入度}{2}\) 的边。
- 直接跑网络流,如果满流,则有解,否则无解,输出 \(-1\)。
- 如果有解的话,就遍历一下无向边,如果在网络流中,这条边满流了,那么就把它反向。最后输出方案即可。
原理:
个人认为最难理解的就是第二点,为什么要这样连边呢?欧拉回路要求的就是使得图中每个点的入度等于出度。如果一个点的入度大于出度,把它与源点相连,流从连接他的边流出,就相当于改变了边的方向,调整了出入度。另一种情况也同理。
无解的时候,为什么图不能满流?无解,就相当于网络流无法将图中的每个点调整为出度等于入度,因此就无解了。
放个板子:
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e6;
const int INF = 0x3f3f3f3f;
int id[MAXN + 5],n,m,tot,deg[MAXN + 5],node[MAXN + 5],cno,head[MAXN + 5],s,t,dis[MAXN + 5],cur[MAXN + 5],ans[MAXN + 5],cnt;
bool vis[MAXN + 5];
vector<int> lsh;
struct E{
int l,r,w;
}e[MAXN + 5];
struct EDGE{
int u,v,w,next;
}edge[MAXN + 5];
void ADD(int u,int v,int w){
++tot;
edge[tot].u = u;
edge[tot].v = v;
edge[tot].w = w;
edge[tot].next = head[u];
head[u] = tot;
}
void add(int u,int v,int w){
ADD(u,v,w);
ADD(v,u,0);
}
bool bfs(){
queue<int> q;
q.push(s);
memset(dis,0,sizeof dis);
dis[s] = 1;
while(!q.empty()){
int u = q.front();
q.pop();
for(int i = head[u]; i; i = edge[i].next){
int v = edge[i].v;
if(!dis[v] && edge[i].w > 0){
dis[v] = dis[u] + 1;
q.push(v);
}
}
}
if(dis[t])return 1;
return 0;
}
int dfs(int u,int dist){
if(u == t)return dist;
for(int &i = cur[u]; i; i = edge[i].next){
int v = edge[i].v;
if(dis[v] == dis[u] + 1 && edge[i].w > 0){
int di = dfs(v,min(edge[i].w,dist));
if(di > 0){
edge[i].w -= di;
edge[i ^ 1].w += di;
return di;
}
}
}
return 0;
}
int dinic(){
int an = 0;
while(bfs()){
for(int i = s; i <= t; i++){cur[i] = head[i];}
while(int di = dfs(s,INF))an += di;
}
return an;
}
int main(){
freopen("wait.in","r",stdin);
freopen("wait.out","w",stdout);
tot = 1;
scanf("%d%d",&m,&n);
for(int i = 1; i <= m; i++){
scanf("%d%d%d",&e[i].l,&e[i].r,&e[i].w);
e[i].r++;
lsh.push_back(e[i].l);
lsh.push_back(e[i].r);
}
sort(lsh.begin(),lsh.end());
lsh.erase(unique(lsh.begin(),lsh.end()),lsh.end());
for(int i = 1; i <= m; i++){
e[i].l = lower_bound(lsh.begin(),lsh.end(),e[i].l) - lsh.begin() + 1;
e[i].r = lower_bound(lsh.begin(),lsh.end(),e[i].r) - lsh.begin() + 1;
}
for(int i = 1; i <= m; i++){
if(e[i].w == -1){
add(e[i].l,e[i].r,1);
ans[tot / 2] = 1;
deg[e[i].l]++;
deg[e[i].r]--;
id[i] = tot;
}
else if(e[i].w == 1)deg[e[i].l]++,deg[e[i].r]--;
else deg[e[i].r]++,deg[e[i].l]--;
}
s = 0,t = lsh.size() + 1;
for(int i = 1; i <= lsh.size(); i++){
if(deg[i] >= 0){
add(s,i,deg[i] / 2);
cnt += deg[i] / 2;
}
else{
add(i,t,(-deg[i] / 2));
}
}
int k;
k = dinic();
if(k < cnt){
cout << "-1";
return 0;
}
for(int i = 1; i <= m; i++){
if(e[i].w == -1) cout << (edge[id[i]].w ^ 1) << " ";
else cout << e[i].w << " ";
}
}
//4 1000000000
//1 9 -1
//2 6 -1
//1 8 -1
//1 7 -1
利用网络流的动态调整功能,可以解决这样的很多问题。