【洛谷3511】[POI2010] MOS-Bridges(混合图欧拉回路)
- 给定一张\(n\)个点\(m\)条边的图,每条边正走与逆走有着不同的边权。
- 求一条所经最大边权最小的欧拉回路,要求构造一组方案。
- \(n\le10^3,m\le2\times10^3\)
二分答案
很容易想到二分答案\(x\)。
那么,对于有一个边权大于\(x\)的边我们可以直接定向看作有向边,而两个边权都小于等于\(x\)的我们依然只能看作无向边。
这种在同时包含有向边和无向边的图中求欧拉回路的问题,称作“混合图欧拉回路”。
混合图欧拉回路
一个经典(?)问题。(好吧,实际上只是因为我前段时间刚好看到过这个奇怪的东西)
对于有向边,我们直接连上;对于无向边\((x,y)\),我们先假定它的方向为\(x\rightarrow y\)。
这样一来统计出每个点入度\(-\)出度值\(\Delta_i\),而有向图存在欧拉回路的判定条件是所有点\(\Delta_i=0\)。
由于无向边我们是强行定向的,实际上可以进行调整,而将\(x\rightarrow y\)反向成\(y\rightarrow x\)相当于使得\(\Delta_x\)增大\(2\),\(\Delta_y\)减少\(2\)。
像这种复杂的关系我们可以用网络流来表示:
- 对于点,从超级源向\(\Delta_i>0\)的点连一条容量为\(\frac{\Delta_i}2\)的边,从\(\Delta_i<0\)的点向超级汇连一条容量为\(\frac{-\Delta_i}2\)的边。(由于每个点度数肯定为偶数,因此\(\Delta_i\)肯定为偶数)
- 对于边,从\(y\)向\(x\)连一条容量为\(1\)的边。
只要跑一次网络流看看能不能跑满就能确定是否存在欧拉回路,然后只要看一下每条边在网络流中对应的边是否被流就能确定这条边的方向。
知道了所有边的方向,最后只要跑一遍有向图欧拉回路求方案就好了。
代码:\(O(nmlogn)\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 1000
#define M 2000
#define INF (int)1e9
#define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y,--deg[x],++deg[y])
using namespace std;
int n,m,ee,lnk[N+5];struct edge {int to,nxt;}e[M+5];struct line {int x,y,p,q;}w[M+5];
namespace FastIO
{
#define FS 100000
#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
char oc,FI[FS],*FA=FI,*FB=FI;
Tp I void read(Ty& x) {x=0;W(!isdigit(oc=tc()));W(x=(x<<3)+(x<<1)+(oc&15),isdigit(oc=tc()));}
Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
}using namespace FastIO;
namespace D//网络流
{
#define PS (N+2)
#define ES (N+M)
#define s (n+1)
#define t (n+2)
#define adde(x,y,f) (e[ee].nxt=lnk[x],e[lnk[x]=ee].to=y,e[ee++].F=f)
int ee,lnk[PS+5],cur[PS+5];struct edge {int to,nxt,F;}e[2*ES+5];
I void Cl() {ee=2;for(RI i=1;i<=t;++i) lnk[i]=0;}
I void Add(CI x,CI y,CI f) {adde(x,y,f),adde(y,x,0);}
int q[PS+5],d[PS+5];I bool BFS()
{
RI i,k,H,T;for(i=1;i<=t;++i) d[i]=0;d[q[H=T=1]=s]=1;W(H<=T&&!d[t])
for(i=lnk[k=q[H++]];i;i=e[i].nxt) !d[e[i].to]&&e[i].F&&(d[q[++T]=e[i].to]=d[k]+1);
return d[t];
}
I int DFS(CI x=s,RI f=INF)
{
if(x==t||!f) return f;RI i,o,g=0;for(i=cur[x];i;i=e[i].nxt)
{
if((d[x]+1)^d[e[i].to]||!e[i].F||!(o=DFS(e[i].to,min(f,e[i].F)))) continue;
if(e[i].F-=o,e[i^1].F+=o,g+=o,!(f-=o)) break;
}return cur[x]=i,g;
}
I int MaxFlow() {RI g=0;W(BFS()) memcpy(cur,lnk,sizeof(lnk)),g+=DFS();return g;}
}
int id[M+5],deg[N+5];I bool Check(CI x)//检验答案:混合图欧拉回路
{
RI i;for(i=1;i<=n;++i) deg[i]=0;for(D::Cl(),i=1;i<=m;++i)
--deg[w[i].x],++deg[w[i].y],w[i].q<=x&&(id[i]=D::ee,D::Add(w[i].y,w[i].x,1),0);//无向边先强制定向,然后在网络流中连边表示可后悔
RI k=0;for(i=1;i<=n;++i) deg[i]>0&&(D::Add(s,i,deg[i]>>1),k+=deg[i]>>1),deg[i]<0&&(D::Add(i,t,(-deg[i])>>1),0);//根据入点-出度的正负和超级源汇连边
return D::MaxFlow()==k;//跑最大流判断能否流满
}
int T,S[M+5];I void Euler(CI x=1) {for(RI i=lnk[x];i;i=lnk[x]) lnk[x]=e[i].nxt,Euler(e[i].to),S[++T]=i;}//有向图欧拉回路
I void Print(CI x)//输出方案
{
Check(x);for(RI i=1;i<=m;++i) w[i].q<=x?(D::e[id[i]].F?add(w[i].x,w[i].y):add(w[i].y,w[i].x)):add(w[i].x,w[i].y);//根据网络流中边是否被流给边定向
printf("%d\n",x),Euler();W(T) printf("%d ",S[T--]);//倒序输出
}
int main()
{
RI i,l=0,r=0;for(read(n,m),i=1;i<=m;++i) read(w[i].x,w[i].y,w[i].p,w[i].q),
w[i].p>w[i].q&&(swap(w[i].x,w[i].y),swap(w[i].p,w[i].q),0),l=max(l,w[i].p),r=max(r,w[i].q);//方便起见,强制正走代价较小
for(i=1;i<=m;++i) ++deg[w[i].x],++deg[w[i].y];for(i=1;i<=n;++i) if(deg[i]&1) return puts("NIE"),0;//判定是否存在无向图欧拉回路
RI mid;W(l^r) Check(mid=l+r-1>>1)?r=mid:l=mid+1;return Print(r),0;//二分答案
}
待到再迷茫时回头望,所有脚印会发出光芒