网络流学习笔记
网络流分为两部分:网络和流
网络
- 网络就是一个有向图,每条边的边权称为容量,额外的,每个网络都具有一个源点和汇点
流
- 如果把网络比作一个个水管组成的网络,流就是水流,即每条边上的流都不能超过它的容量
最大流
最大流是网络流中常见的问题,即假设从源点流出的流量无限大,求最终能流入汇点的流量最大为多少
常用的算法是Dinic
Dinic
首先,我们要知道几个性质:
- 对于任何一条流,总有 流量\(\leq\)容量
- 对于任何一个非源点汇点的点,它的入度和出度一定相等
- 对于任意一条有向边,一定有$flow[i][j] = -flow[j][i] (flow[i][j]表示i到j的流量) $
了解这几个性质后就可以进一步开始求解最大流了
这里我们介绍的是Dinic算法
定义:
增广路 :一条源点到汇点的路径且其中任意一条边的容量都不为0
残余网络 :原网络中每条边除去已经流过的流量的剩余网络
由此,我们可以口胡一个算法:
1、找到一条增广路,考虑是否流过这条增广路
2、若流过,则将这条增广路上所有边全部减去流量
我们发现这样做实在是太太太太太太太太慢了,所以我们考虑优化
我们考虑对于一个网络建立一个反悔机制,即对每一条边建立一个反边,当一条边被增广时,则将其反边加上流量,这样的话,我们就可以在增广其他增广路时经过它的反边,这样的话,它的流量就与之前那条增广路抵消了
具体步骤如下:
1、找到一条从源点到汇点的路径,使得路径上任意一条边的残量>0(注意是大于而不是大于等于,这意味着这条边还可以分配流量),这条路径便称为增广路
2、找到这条路径上最小的F[u][v](我们设F[u][v]表示u->v这条边上的残量即剩余流量),下面记为flow
3、将这条路径上的每一条有向边u->v的残量减去flow,同时对于起反向边v->u的残量加上flow
4、重复上述过程,直到找不出增广路,此时我们就找到了最大流
但我们发现这样做还是不优,因为这样每次只能处理一条增广路,我们考虑一次处理多个增广路,这时便引出了Dinic算法
Dinic算法相比于上个算法的优化体现在一次dfs就处理了多次增广,那这应该怎么做呢
我们在每次处理增广路时,对于残余网络建立分层图,每次由now推出的nex的深度必须为now的深度+1
最小割
最小割是指在网络中割掉边权和最小的一条或几条边使得网络不连通
由最大流最小割定理可得,最小割=最大流,因此直接用Dinic求解
P2598 [ZJOI2009]狼和羊的故事
题意
给定一个 \(N\times M\) 的矩阵,矩阵上每一个点可能是狼、空地或者羊,你要在某些点的某几个边界方篱笆使得任意狼、羊不能互通。
Solution
对于这种只有两个种类的题,我们考虑将源点看作一种,汇点看作另一种
在这道题里,就可以将源点看作羊,汇点看作狼
即连向源点的边表示这个点是羊的地盘,连向汇点的边表示这个点是狼的地盘
我们发现,我们不能存在一条从羊到狼的路径
因此,题意可转化为:割掉一些边使得s不与t联通
这时就会发现这变成了一个最小割的板子,那么具体应该怎么建图呢?
我们肯定是要将羊与狼连边的,我们将这条边割掉的意义即为在这两个点之间修栅栏
接下来还要考虑中立地区
因为中立地区可以使羊通过\(羊 \rightarrow 中立地区 \rightarrow 狼\)准确送入狼嘴里
因此羊与中立地区,中立地区与狼都要建边
然后对于中立地区内,任何位置都能建栅栏,你无法保证那种方法最优
所以我们在中立地区内也建边,让代码自己来判断割哪条边
#include <queue>
#include <ctime>
#include <cstdio>
#include <cstring>
#include <iostream>
#define file(a) freopen(#a".in","r",stdin),freopen(#a".out","w",stdout)
using namespace std;
const int maxn=100+5,maxm=4000000+5,inf=0x3f3f3f3f;
const int d[4][2]={1,0,0,1,-1,0,0,-1};
int n,m,mp[maxn][maxn],beg[maxn*maxn],tot=1,s,t,ans,cur[maxn*maxn],level[maxn*maxn];
struct edge{
int nex,to,w;
}e[maxm*2];
void add(int x,int y,int z) {
e[++tot]=(edge){beg[x],y,z};
beg[x]=tot;
e[++tot]=(edge){beg[y],x,0};
beg[y]=tot;
}
int read() {
int x=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9') {if (ch=='-') f=-1;ch=getchar();}
while(ch<='9' && ch>='0') {x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
return x*f;
}
int ID(int i,int j) {
return (i-1)*m+j;
}
bool bfs() {
queue<int>q;
q.push(s);
memset(level,0,sizeof(level));
level[s]=1;
while(!q.empty()) {
int now=q.front();q.pop();
for (int i=beg[now];i;i=e[i].nex) {
int nex=e[i].to;
if (!e[i].w || level[nex]) continue;
level[nex]=level[now]+1;
q.push(nex);
if (nex==t) return true;
}
}
return false;
}
int dinic(int now,int flow) {
if (now==t) return flow;
int rest=flow;
for (int i=beg[now];i && rest;i=e[i].nex) {
int nex=e[i].to;
if (e[i].w && level[nex]==level[now]+1) {
int inc=dinic(nex,min(rest,e[i].w));
if (!inc) level[nex]=0;
e[i].w-=inc;
e[i^1].w+=inc;
rest-=inc;
}
}
return flow-rest;
}
int main() {
file(luogu2598);
n=read();m=read();s=0;t=n*m+1;
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++)
mp[i][j]=read();
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++) {
if (mp[i][j]==2) add(s,ID(i,j),inf);
else if (mp[i][j]==1) add(ID(i,j),t,inf);
for (int k=0;k<4;k++) {
int xx=i+d[k][0];
int yy=j+d[k][1];
// cout<<i<<' '<<j<<' '<<xx<<' '<<yy<<endl;
if (xx<1 || xx>n || yy<1 || yy>m) continue;
if (mp[i][j]==0 && mp[xx][yy]!=2)
add(ID(i,j),ID(xx,yy),1);//cout<<i<<' '<<j<<' '<<mp[i][j]<<' '<<xx<<' '<<yy<<' '<<mp[xx][yy]<<endl;
if (mp[i][j]==2 && mp[xx][yy]!=2)
add(ID(i,j),ID(xx,yy),1);//cout<<i<<' '<<j<<' '<<mp[i][j]<<' '<<xx<<' '<<yy<<' '<<mp[xx][yy]<<endl;
}
}
while(bfs()) ans+=dinic(s,inf);
cout<<ans<<endl;
cerr<<clock()<<endl;
return 0;
}
最小割求最大权闭合子图
定义:
最大权闭合子图 : 有一个有向图,每一个点都有一个权值(可以为正或负或0),选择一个权值和最大的子图,使得每个点的后继都在子图里面,这个子图就叫最大权闭合子图。
我们可以建两个点,一个表示加入子图,一个表示不加入子图,分别作为源点和汇点
我们发现正权值越多越好,负权值越少越好
所以我们源点向每个正权值的点连边,边权为权值,每个负权值的点向汇点连边,边权为权值的绝对值
然后对建出来的图跑最小割,此时跑出的最小割=不在子图中的正权值和+|在子图中的负权值和|
我们需要的答案=正权值和-不在子图中的正权值和-|在子图中的负权值和|=正权值和-最小割
POJ2987 Firing
题意:
你要解雇一些员工,其中有一些限制。即若要解雇x则必须解雇y。解雇每个人都会有一定的收益。现在要让这个收益最大。输出解雇的人数和最大收益。
Solution
最大收益就是求最大权闭环子图,直接跑最小割,最多人数则是参与网络剩下的点,跑遍dfs就行了
#include <queue>
#include <ctime>
#include <cstdio>
#include <cstring>
#include <iostream>
#define int long long
#define file(a) freopen(#a".in","r",stdin),freopen(#a".out","w",stdout)
using namespace std;
const int maxn=5000+5,maxm=60000+5,inf=0x3f3f3f3f3f3f3f3f;
int s,t,n,m,sum,beg[maxn],tot=1,a[maxn],ans,level[maxn],vis[maxn],res;
struct edge{
int nex,to,w;
}e[maxm*2];
void add(int x,int y,int z) {
e[++tot]=(edge){beg[x],y,z};
beg[x]=tot;
e[++tot]=(edge){beg[y],x,0};
beg[y]=tot;
}
int read() {
int x=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9') {if (ch=='-') f=-1;ch=getchar();}
while(ch<='9' && ch>='0') {x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
return x*f;
}
bool bfs() {
memset(level,0,sizeof(level));
level[s]=1;
queue<int>q;
q.push(s);
while(!q.empty()) {
int now=q.front();q.pop();
for (int i=beg[now];i;i=e[i].nex) {
int nex=e[i].to;
if (!e[i].w || level[nex]) continue;
level[nex]=level[now]+1;
q.push(nex);
if (nex==t) return true;
}
}
return false;
}
int Dinic(int now,int flow) {
if (now==t) return flow;
int rest=flow;
for (int i=beg[now];i && rest;i=e[i].nex) {
int nex=e[i].to;
if (e[i].w && level[nex]==level[now]+1) {
int inc=Dinic(nex,min(rest,e[i].w));
if (!inc) level[nex]=0;
e[i].w-=inc;
e[i^1].w+=inc;
rest-=inc;
}
}
return flow-rest;
}
int search(int now) {
vis[now]=1;
for (int i=beg[now];i;i=e[i].nex) {
int nex=e[i].to;
if (e[i].w>0 && !vis[nex]) {search(nex);res++;}
}
return res;
}
signed main() {
file(POJ2987);
while(cin>>n>>m) {
mm s=n+1;t=n+2;
tot=1;ans=0,res=0;sum=0;
memset(beg,0,sizeof(beg));
memset(vis,0,sizeof(vis));
for (int i=1;i<=n;i++) a[i]=read();
for (int i=1;i<=m;i++) {
int x=read(),y=read();
add(x,y,inf);
}
for (int i=1;i<=n;i++) {
if (a[i]>0) {add(s,i,a[i]);sum+=a[i];}
else add(i,t,-a[i]);
}
while(bfs()) ans+=Dinic(s,inf);
search(s);
cout<<res<<' '<<sum-ans<<endl;
}
cerr<<clock()<<endl;
return 0;
}
CF311E Biologist
题意:
给定一个长度为\(n\)的0/1序列,第\(i\)个位置的数为\(a_i\),改变第\(i\)个位置的数需要付出\(b_i\)点代价
现有\(m\)个任务,对于一个任务\(i\),它的要求如下:
要求\(k\)个位置全部为0/1,其中这\(k\)个位置已经给出
完成第\(i\)项任务,将获得\(w\)点价值
有的任务如果没有完成,还将扣除\(g\)点价值
求最大总价值
Solution
我们将源点看作将数为1,将汇点看作数为0
考虑一个位置
连s这条边表示这个位置上的数为0,割s这条边表示这个位置上的数不为0
连t这条边表示这个位置上的数为1,割s这条边表示这个位置上的数不为1
那么若这个位置上的数为0,则将它连向t的边设为0,连向s的边设为b_i,表示它为0需要0代价,为1需要\(b_i\)代价
对于每一个要求,若要求为0,则从s向其连边,边权为w(w+g),并将其向所有要求位置连边,边权为inf
则割掉与s的边表示不满足这个要求,否则割掉所有要求的点向t的连边,表示满足这个要求
反之亦然
至此,我们发现题目变成了一个最大权闭合子图,直接跑网络流
答案为\(\sum_{i=1}^{m}w_i - 最小割\)
#include <queue>
#include <ctime>
#include <cstdio>
#include <cstring>
#include <iostream>
#define int long long
#define file(a) freopen(#a".in","r",stdin),freopen(#a".out","w",stdout)
using namespace std;
const int maxn=3e4+5,inf=0x3f3f3f3f3f3f3f3f;
int n,m,g,a[maxn],tot=1,beg[maxn],s,t,v[maxn],ans,sum,level[maxn];
struct edge{
int nex,to,w;
}e[maxn*200];
void add(int x,int y,int z) {
e[++tot]=(edge){beg[x],y,z};
beg[x]=tot;
e[++tot]=(edge){beg[y],x,0};
beg[y]=tot;
}
int read() {
int x=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9') {if (ch=='-') f=-1;ch=getchar();}
while(ch<='9' && ch>='0') {x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
return x*f;
}
bool bfs() {
queue<int>q;
memset(level,0,sizeof(level));
level[s]=1;
q.push(s);
while(!q.empty()) {
int now=q.front();q.pop();
for (int i=beg[now];i;i=e[i].nex) {
int nex=e[i].to;
if (!e[i].w || level[nex]) continue;
level[nex]=level[now]+1;
q.push(nex);
if (nex==t) return true;
}
}
return false;
}
int dinic(int now,int flow) {
if (now==t) return flow;
int rest=flow;
for (int i=beg[now];i && rest;i=e[i].nex) {
int nex=e[i].to;
if (e[i].w && level[nex]==level[now]+1) {
int inc=dinic(nex,min(rest,e[i].w));
if (!inc) level[nex]=0;
e[i].w-=inc;
e[i^1].w+=inc;
rest-=inc;
}
}
return flow-rest;
}
signed main() {
file(CF311E);
n=read();m=read();g=read();s=0;t=n+m+1;
for (int i=1;i<=n;i++) a[i]=read();
for (int i=1;i<=n;i++) {
v[i]=read();
if (a[i]==0) {
add(s,i,v[i]);
add(i,t,0);
}
else {
add(s,i,0);
add(i,t,v[i]);
}
}
for (int i=1;i<=m;i++) {
int v=read(),w=read(),cnt=read();
for (int j=1;j<=cnt;j++) {
int x=read();
if (v==0) add(n+i,x,inf);
else add(x,n+i,inf);
}
int op=read();sum+=w;
if (v==0) add(s,n+i,w+op*g);
else add(n+i,t,w+op*g);
}
while(bfs()) ans+=dinic(s,inf);
cout<<sum-ans<<endl;
cerr<<clock()<<endl;
return 0;
}
P4313 文理分科 & P1646 happiness
双倍经验题
题意:
\(n×m\) 矩阵上的点,分成两部分,每个点分配后会有一个分数,上下左右以及中间 的五个点分到一起会有额外的分数,可以是0,求分配后最大的分数
Solution
考虑源点的意义为这个点选文科,汇点的意义为这个点选理科,对于额外的分数,要求选文科的从源点向它连一条边,并将它连向每个要求的位置,要求选理科的同理,那么题目就被转化为了一个最大权闭合子图,直接跑最小割就行了
#include <queue>
#include <ctime>
#include <cstdio>
#include <cstring>
#include <iostream>
#define int long long
#define file(a) freopen(#a".in","r",stdin),freopen(#a".out","w",stdout)
using namespace std;
const int maxn=2e5+5,maxm=3e7+5,inf=0x3f3f3f3f3f3f3f3f;
int n,m,beg[maxn],tot=1,s,t,sum,level[maxn],ans;
struct edge{
int nex,to,w;
}e[maxm];
void add(int x,int y,int z,int i,int j) {
if (i<1 || i>n || j<1 || j>m) return ;
e[++tot]=(edge){beg[x],y,z};
beg[x]=tot;
e[++tot]=(edge){beg[y],x,0};
beg[y]=tot;
}
int read() {
int x=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9') {if (ch=='-') f=-1;ch=getchar();}
while(ch<='9' && ch>='0') {x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
return x*f;
}
int ID(int i,int j) {
return (i-1)*m+j;
}
bool bfs() {
queue<int>q;
memset(level,0,sizeof(level));
q.push(s);
level[s]=1;
while(!q.empty()) {
int now=q.front();q.pop();
for (int i=beg[now];i;i=e[i].nex) {
int nex=e[i].to;
if (!e[i].w || level[nex]) continue;
level[nex]=level[now]+1;
q.push(nex);
if (nex==t) return true;
}
}
return false;
}
int dinic(int now,int flow) {
if (now==t) return flow;
int rest=flow;
for (int i=beg[now];i && rest;i=e[i].nex) {
int nex=e[i].to;
if (e[i].w && level[nex]==level[now]+1) {
int inc=dinic(nex,min(rest,e[i].w));
if (!inc) level[nex]=0;
e[i].w-=inc;
e[i^1].w+=inc;
rest-=inc;
}
}
return flow-rest;
}
signed main() {
file(luogu4313);
n=read();m=read();s=0;t=4*n*m+1;
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++) {
int x=read();
sum+=x;
add(s,ID(i,j),x,i,j);
}
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++) {
int x=read();
sum+=x;
add(ID(i,j),t,x,i,j);
}
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++) {
int x=read();
sum+=x;
add(s,n*m+ID(i,j),x,i,j);
add(n*m+ID(i,j),ID(i,j),inf,i,j);
add(n*m+ID(i,j),ID(i-1,j),inf,i-1,j);
add(n*m+ID(i,j),ID(i,j-1),inf,i,j-1);
add(n*m+ID(i,j),ID(i+1,j),inf,i+1,j);
add(n*m+ID(i,j),ID(i,j+1),inf,i,j+1);
}
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++) {
int x=read();
sum+=x;
add(2*n*m+ID(i,j),t,x,i,j);
add(ID(i,j),2*n*m+ID(i,j),inf,i,j);
add(ID(i-1,j),2*n*m+ID(i,j),inf,i-1,j);
add(ID(i,j-1),2*n*m+ID(i,j),inf,i,j-1);
add(ID(i+1,j),2*n*m+ID(i,j),inf,i+1,j);
add(ID(i,j+1),2*n*m+ID(i,j),inf,i,j+1);
}
while(bfs()) ans+=dinic(s,inf);
cout<<sum-ans<<endl;
cerr<<clock()<<endl;
return 0;
}
当前弧优化
一个很实用的优化,也很好写
因为一条边已经跑过的增广路全部跑完了,也不会在经过这条边
也就是说,这条边已经流满了
因此下次在跑的时候就不用经过这条边了
所以直接从已经流满的边开始流就行了
P6054 [RC-02] 开门大吉
题意
n 位选手去参加节目“开门大吉”。共有 \(m\) 套题,每套题包含 \(p\) 个题目,第 \(i\) 位选手答对第 \(j\) 套题中第 \(k\) 道的概率为 \(f_{i,j,k}\)。
若一位选手答对第 \(i\) 题,会在已得到奖励的基础上,再得到 \(c_i\) 元奖励。选手总是从第一道开始,按顺序答题的。
同时,为了防止过多的选手做同一套题,还有 \(y\) 条形如“第 \(i\) 位选手的套题编号必须至少比第 \(j\) 位的大 \(k\)”的限制。
你需要给每一位选手分配一套题(不同选手可以相同),使得所有人的期望奖励和最小。
Solution
首先,需要注意的是,答一套题时,只有答完了第\(i\)道,才能答第\(i+1\)道题,所以概率是要累乘的
其次,\(k\)的范围是在\(-m\)到\(m\)之间的,我tm因为这个傻逼东西调了3天
然后我们就可以考虑建边了
首先,因为每个选手都可以选\(m\)套题中的1个,如果每个选手一个点,每套题一个点显然是不好处理要求的
那么我们考虑拆点
我们将第i个选手拆成\((i,j)\)表示第\(i\)个选手选第\(j\)套题,然后将\((i,j)\)向\((i,j+1)\)连一条边,边权为第\(i\)个人做第\(j\)套题的期望得分
这时,割掉\((i,j)\)向\((i,j+1)\)这条边的意义就是第\(i\)个选手做第\(j\)套题
然后我们将源点连向选手\(i\)的\((i,1)\),选手\(i\)的\((i,m+1)\)连向汇点\((1 \leq i \leq n)\)
这时候我们就要开始考虑要求了,对于一个要求\(“i比j做的题大k”\)的条件,我们将\((j,x)\)向\((i,x+k)\)连边,边权为\(inf\),这样的话,就只有割掉\((j,y) (y \leq x)\)或\((i,y)(y \geq x)\)才能使图不连通
但是这是有问题的
当有一个要求为\(“i比j做的题大x”\),一个要求为\(“j比k做的题大x”\)
我们会连\((j,u) \rightarrow (i,u+x)\)和\((k,v) \rightarrow (j,v+x)\)两条边\((v+x>u)\)
这时候,我们割掉\((j,u-1) \rightarrow (j,u)\) 和 \((j,v+x) \rightarrow (j,v+x+1)\)可以使图不连通
但很显然这样是不对的,因为一个选手不能做两道题,那应该怎么办呢?
我们考虑建立反边,即\((i,j+1) \rightarrow (i,j)\),边权为inf
这就能很好的避免上面那种情况,然后这道题就差不多做完了
剩下的就是些优化:
-
当整个图无解时,我们可以在中途发现\(flow>inf\)时就退出来
-
当前弧优化
#include <queue>
#include <ctime>
#include <cstdio>
#include <cstring>
#include <iostream>
#define file(a) freopen(#a".in","r",stdin),freopen(#a".out","w",stdout)
using namespace std;
const int maxn=80+5,maxm=5e5+10;
const double inf=1e11,eps=1e-9;3
int T,n,m,p,N,beg[maxm],tot=1,level[maxm],s,t,c[maxn],cur[maxm];
double ans,g[maxn][maxn];
struct edge{
int nex,to;
double w;
}e[maxm*2];
void add(int x,int y,double z) {
e[++tot]=(edge){beg[x],y,z};
beg[x]=tot;
e[++tot]=(edge){beg[y],x,0.};
beg[y]=tot;
}
int ID(int i,int j) {
return (j-1)*n+i;
}
int read() {
int x=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9') {if (ch=='-') f=-1;ch=getchar();}
while(ch<='9' && ch>='0') {x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
return x*f;
}
bool bfs() {
queue<int>q;
q.push(s);
memset(level,0,sizeof(level));
level[s]=1;
while(!q.empty()) {
int now=q.front();q.pop();
for (int i=beg[now];i;i=e[i].nex) {
int nex=e[i].to;
if (e[i].w<eps || level[nex]) continue;
level[nex]=level[now]+1;
q.push(nex);
}
}
return level[t];
}
double Dinic(int now,double flow) {
if (now==t || flow<eps) return flow;
double rest=flow;
for (int &i=cur[now];i && rest;i=e[i].nex) {
int nex=e[i].to;
if (e[i].w>eps && level[nex]==level[now]+1) {
double inc=Dinic(nex,min(e[i].w,rest));
if (!inc) level[nex]=0;
e[i].w-=inc;
e[i^1].w+=inc;
rest-=inc;
if (rest<eps) break;
}
}
return flow-rest;
}
int main() {
file(luogu6054);
T=read();
while(T--) {
memset(beg,0,sizeof(beg));
memset(g,0,sizeof(g));
tot=1;ans=0.;
n=read();m=read();p=read();N=read();
s=n*(m+1)+1;t=n*(m+1)+2;
for (int i=1;i<=p;++i) c[i]=read();
for (int i=1;i<=m;++i)
for (int j=1;j<=n;++j) {
double v=1.;
for (int k=1;k<=p;++k) {
double d;
scanf("%lf",&d);v*=d;
g[j][i]+=1.*c[k]*v;
}
}
for (int i=1;i<=n;++i) {
add(s,ID(i,1),inf);
add(ID(i,m+1),t,inf);
for (int j=1;j<=m;++j) {
add(ID(i,j),ID(i,j+1),g[i][j]);
add(ID(i,j+1),ID(i,j),inf);
}
}
for (int i=1;i<=N;++i) {
int u=read(),v=read(),k=read();
for (int j=1;j<=m;++j)
if (j+k>=1 && j+k<=m+1)
add(ID(v,j),ID(u,j+k),inf);
}
while(bfs()) {
memcpy(cur,beg,sizeof(beg));
ans+=Dinic(s,inf);
if (ans>inf) break;
}
if (ans>inf) puts("-1");
else printf("%.7lf\n",ans);
}
cerr<<clock()<<endl;
return 0;
}