标签:work alt date() 流量 网络流 复杂 利用 oid 精度
一个网络\(G=(V,E)\)是一张有向图,图中每条有向边\((x,y)\in E\)都有一个给定的权值\(c(x,y)\),称为边的容量。特别地,若\((x,y) \notin E\),则\(c(x,y)=0\)。图中还有两个指定的特殊节点\(S,T \in V(S \neq T)\)分别被称为源点和汇点。
设\(f(x,y)\)是定义在节点二元组\((x \in V,y \in V)\)上的实数函数,且满足:
容量限制:\(f(x,y) \leq c(x,y)\)
斜对称:\(f(x,y)=-f(y,x)\)
流量守恒:\(\forall x \neq S,\ x \neq T,\ \sum_{(u,x)\ \in E} f(u,x) = \sum_{(x,v)\ \in E}f(x,v)\)
\(f\)称为网络的流函数,对于\((x,y) \in E\),\(f(x,y)\)称为边的流量,\(c(x,y)-f(x,y)\)称为边的剩余流量
\(\sum_{(S,v)\ \in E} f(S,v)\)称为整个网络的流量(\(S\)为源点)
若一条从源点\(S\)到汇点\(T\)的路径上各条边的剩余容量都大于\(0\),则称这条路径为一条增广路
\(EK\)算法为用\(bfs\)不断寻找增广路,直到网络上不存在增广路为止
用\(bfs\)找到任意一条从\(S\)到\(T\)的路径,记录路径上各边的剩余容量的最小值,则网络的流量就可以增加这个最小值
利用邻接表成对存储来实现\((x,y)\)剩余容量的减小,\((y,x)\)剩余容量的增大
时间复杂度上界为\(O(nm^2)\),一般可以处理\(1e3 \sim 1e4\)规模的网格
\(code\):
bool bfs()
{
memset(vis,0,sizeof(vis));
queue<int> q;
q.push(s);
vis[s]=true;
res[s]=inf;
while(!q.empty())
{
int x=q.front();
q.pop();
for(int i=head[x];i;i=e[i].nxt)
{
int y=e[i].to,v=e[i].v;
if(vis[y]||!v) continue;
res[y]=min(res[x],v);
pre[y]=i;
q.push(y);
vis[y]=true;
}
}
return vis[t];
}
void update()
{
int x=t;
while(x!=s)
{
int i=pre[x];
e[i].v-=res[t];
e[i^1].v+=res[t];
x=e[i^1].to;
}
ans+=res[t];
}
......
while(bfs()) update();
在任意时刻,网络中所有节点以及剩余容量大于\(0\)的边构成的子图称为残量网络
\(Dinic\)算法引入分层图的概念,\(d[x]\)表示从\(S\)到\(x\)最少需要经过的边数,为了方便处理设\(d[S]=1\),分层图为残量网络中满足\(d[y]=d[x]+1\)的边\((x,y)\)构成的子图
时间复杂度上界为\(O(n^2m)\),一般可以处理\(1e4 \sim 1e5\)规模的网格,求解二分图最大匹配的时间复杂度为\(O( \sqrt nm)\)
在\(Dinic\)算法中还可以加入若干剪枝来优化
\(res\),表示当前节点的流量剩余,若\(res \leqslant 0\),停止寻找增广路
\(cur[x]\),表示当到达到\(x\)节点时,直接从\(cur[x]\)对应的边开始遍历,实际表示上一次从\(x\)遍历到了哪一条边,因为在这之间的边都已经被彻底增广过了,所以可以直接跳转,称为当前弧优化
\(code\):
bool bfs()
{
memcpy(cur,head,sizeof(head));
memset(d,0,sizeof(d));
queue<int> q;
q.push(s);
d[s]=1;
while(!q.empty())
{
int x=q.front();
q.pop();
for(int i=head[x];i;i=e[i].nxt)
{
int y=e[i].to,v=e[i].v;
if(d[y]||!v) continue;
d[y]=d[x]+1;
q.push(y);
}
}
return d[t];
}
int dfs(int x,int lim)
{
if(x==t) return lim;
int res=lim,flow;
for(int &i=cur[x];i;i=e[i].nxt)
{
int y=e[i].to,v=e[i].v;
if(d[y]!=d[x]+1||!v) continue;
if(flow=dfs(y,min(res,v)))
{
res-=flow;
e[i].v-=flow;
e[i^1].v+=flow;
if(!res) break;
}
}
return lim-res;
}
int dinic()
{
int flow,ans=0;
while(bfs())
while(flow=dfs(s,inf))
ans+=flow;
return ans;
}
若要求每条边的所用的流量,可以将原图备份,跑完最大流后,用原图的容量减去当前的剩余容量即可求得所用流量
可以去求解二分图多重匹配
利用最小割,将求解最大收益转化为最小代价
\(code\):
bool spfa()
{
for(int i=1;i<=n;++i) dis[i]=inf;
memset(vis,0,sizeof(vis));
queue<int> q;
q.push(s);
vis[s]=true;
dis[s]=0;
res[s]=inf;
while(!q.empty())
{
int x=q.front();
q.pop();
vis[x]=false;
for(int i=head[x];i;i=e[i].nxt)
{
int y=e[i].to,v=e[i].v,c=e[i].c;
if(dis[y]>dis[x]+c&&v)
{
dis[y]=dis[x]+c;
res[y]=min(res[x],v);
pre[y]=i;
if(!vis[y])
{
vis[y]=true;
q.push(y);
}
}
}
}
return dis[t]!=inf;
}
void update()
{
int x=t;
while(x!=s)
{
int i=pre[x];
e[i].v-=res[t];
e[i^1].v+=res[t];
x=e[i^1].to;
}
ans+=res[t];
sum+=res[t]*dis[t];
}
......
while(spfa()) update();
\(code\):
bool spfa()
{
for(int i=1;i<=n;++i) dis[i]=inf;
memset(vis,0,sizeof(vis));
queue<int> q;
q.push(s);
dis[s]=0;
vis[s]=true;
while(!q.empty())
{
int x=q.front();
q.pop();
vis[x]=false;
for(int i=head[x];i;i=e[i].nxt)
{
int y=e[i].to,v=e[i].v,c=e[i].c;
if(dis[y]>dis[x]+c&&v)
{
dis[y]=dis[x]+c;
if(!vis[y])
{
vis[y]=true;
q.push(y);
}
}
}
}
return dis[t]!=inf;
}
int dfs(int x,int lim)
{
if(x==t) return lim;
vis[x]=true;
int res=lim,flow;
for(int i=head[x];i;i=e[i].nxt)
{
int y=e[i].to,v=e[i].v,c=e[i].c;
if(dis[y]!=dis[x]+c||!v||vis[y]) continue;
if(flow=dfs(y,min(res,v)))
{
res-=flow;
e[i].v-=flow;
e[i^1].v+=flow;
if(!res) break;
}
}
return lim-res;
}
void dinic()
{
int flow;
while(spfa())
while(flow=dfs(s,inf))
ans+=flow,sum+=flow*dis[t];
}
可以去求解二分图带权匹配
\(n\)个点,\(m\)条边的网络,求一个可行解,使得边\((x,y)\)的流量介于\([\ low_{x,y},up_{x,y}\ ]\)之间,并且整个网络满足流量守恒
将\(up_{x,y}-low_{x,y}\)作为容量上界,\(0\)作为容量下界
设\(in[x]=\sum\limits_{i\to x} low(i,x)-\sum\limits_{x\to i} low(x,i)\)
若\(in[x]>0\),则从源点\(S\)向\(x\)连边,容量为\(in[x]\),反之,则从\(x\)向汇点\(T\)连边,容量为\(-in[x]\)
在该网络上求最大流,求完后每条边的流量再加上容量下界即为一种可行流
\(code:\)
bool bfs()
{
memcpy(cur,head,sizeof(head));
memset(d,0,sizeof(d));
queue<int> q;
q.push(s);
d[s]=1;
while(!q.empty())
{
int x=q.front();
q.pop();
for(int i=head[x];i;i=e[i].nxt)
{
int y=e[i].to,v=e[i].v;
if(d[y]||!v) continue;
d[y]=d[x]+1;
q.push(y);
}
}
return d[t];
}
int dfs(int x,int lim)
{
if(x==t) return lim;
int res=lim,k;
for(int &i=cur[x];i;i=e[i].nxt)
{
int y=e[i].to,v=e[i].v;
if(d[y]!=d[x]+1||!v) continue;
if(k=dfs(y,min(res,v)))
{
res-=k;
e[i].v-=k;
e[i^1].v+=k;
if(!res) break;
}
}
return lim-res;
}
int dinic()
{
int flow,ans=0;
while(bfs())
while(flow=dfs(s,inf))
ans+=flow;
return ans;
}
bool check()
{
for(int i=head[s];i;i=e[i].nxt)
if(e[i].v)
return false;
return true;
}
......
for(int i=1;i<=m;++i)
{
int a,b,up;
read(a),read(b),read(low[i]),read(up);
in[a]-=low[i],in[b]+=low[i];
add(a,b,up-low[i]);
}
for(int i=1;i<=n;i++)
{
if(in[i]>0) add(s,i,in[i]);
else add(i,t,-in[i]);
}
dinic();
if(check())
{
puts("YES");
for(int i=1;i<=m;i++) printf("%d\n",e[(i<<1)^1].v+low[i]);
}
else puts("NO");
从\(T\)向\(S\)连一条容量上界为\(inf\),容量下界为\(0\)的边,使有源汇转化为无源汇
在残量网络上再求原源点到原汇点的最大流
\(code:\)
bool bfs()
{
memcpy(cur,head,sizeof(head));
memset(d,0,sizeof(d));
queue<int> q;
q.push(s);
d[s]=1;
while(!q.empty())
{
int x=q.front();
q.pop();
for(int i=head[x];i;i=e[i].nxt)
{
int y=e[i].to,v=e[i].v;
if(d[y]||!v) continue;
d[y]=d[x]+1;
q.push(y);
}
}
return d[t];
}
int dfs(int x,int lim)
{
if(x==t) return lim;
int res=lim,k;
for(int &i=cur[x];i;i=e[i].nxt)
{
int y=e[i].to,v=e[i].v;
if(d[y]!=d[x]+1||!v) continue;
if(k=dfs(y,min(res,v)))
{
res-=k;
e[i].v-=k;
e[i^1].v+=k;
if(!res) break;
}
}
return lim-res;
}
int dinic()
{
int flow,ans=0;
while(bfs())
while(flow=dfs(s,inf))
ans+=flow;
return ans;
}
bool check()
{
for(int i=head[s];i;i=e[i].nxt)
if(e[i].v)
return false;
return true;
}
......
for(int i=1;i<=m;++i)
{
int a,b,up,low;
read(a),read(b),read(low),read(up);
in[a]-=low,in[b]+=low;
add(a,b,up-low);
}
for(int i=1;i<=n;i++)
{
if(in[i]>0) add(s,i,in[i]);
else add(i,t,-in[i]);
}
add(T,S,inf);
dinic();
ans=e[edge_cnt].v;
e[edge_cnt].v=e[edge_cnt^1].v=0;
if(check())
{
s=S,t=T;
printf("%d",ans+dinic());
}
else puts("NO");
先不添加 \(T\) 到 \(S\) 的边,求一次超级源到超级汇的最大流。
然后再添加一条从 \(T\) 到 \(S\) 下界为 \(0\) ,上界为 \(inf\) 的边,在残量网络上再求一次超级源到超级汇的最大流
流经 \(T\) 到 \(S\) 的边的流量就是最小流的值
\(code:\)
bool bfs()
{
memcpy(cur,head,sizeof(head));
memset(d,0,sizeof(d));
queue<int> q;
q.push(s);
d[s]=1;
while(!q.empty())
{
int x=q.front();
q.pop();
for(int i=head[x];i;i=e[i].nxt)
{
int y=e[i].to,v=e[i].v;
if(d[y]||!v) continue;
q.push(y);
d[y]=d[x]+1;
}
}
return d[t];
}
int dfs(int x,int lim)
{
if(x==t) return lim;
int res=lim,k;
for(int &i=cur[x];i;i=e[i].nxt)
{
int y=e[i].to,v=e[i].v;
if(d[y]!=d[x]+1||!v) continue;
if(k=dfs(y,min(res,v)))
{
res-=k;
e[i].v-=k;
e[i^1].v+=k;
if(!res) break;
}
}
return lim-res;
}
int dinic()
{
int k,flow=0;
while(bfs())
{
while(k=dfs(s,inf))
{
flow+=k;
}
}
return flow;
}
bool check()
{
for(int i=head[s];i;i=e[i].nxt)
if(e[i].v)
return false;
return true;
}
......
for(int i=1;i<=m;++i)
{
int a,b,up,low;
read(a),read(b),read(low),read(up);
in[a]-=low,in[b]+=low;
add(a,b,up-low);
}
for(int i=1;i<=n;i++)
{
if(in[i]>0) add(s,i,in[i]);
else add(i,t,-in[i]);
}
dinic();
add(T,S,inf);
dinic();
if(!check())
{
puts("please go home to sleep");
return 0;
}
printf("%d",e[edge_cnt].v);
若有向图 \(G\) 的子图 \(V\) 满足: \(V\) 中顶点的所有出边均指向 \(V\) 内部的顶点,则称 \(V\) 是 \(G\) 的一个闭合子图
若 \(G\) 中的点有点权,则点权和最大的闭合子图称为有向图 \(G\) 的最大权闭合子图
建立源点 \(S\) 和汇点 \(T\) ,源点 \(S\) 连所有点权为正的点,容量为该点点权;其余点连汇点 \(T\) ,容量为该点点权的相反数,对于原图中的边 \((x,y)\) ,连边 \((x,y,inf)\)
最大权闭合图的点权和 \(=\) 所有正权点权值和 \(-\) 最小割
也就是最大收益转化为了最小代价
在残量网络中由源点 \(S\) 能够访问到的点,就构成一个点数最少的最大权闭合图
一个无向图\(G=(V,E)\)的边数\(|E|\)与点数\(|V|\)的比值\(D=\frac{|E|}{|V|}\)称为它的密度
求\(G\)的一个子图\(G^\prime=(V^\prime,E^\prime)\),使得\(D^\prime=\frac{|E^\prime|}{|V^\prime|}\)最大
二分\(g\leqslant\frac{|E|}{|V|}\)
得\(|E|-|V|×g \geqslant0\)
源点\(S\)向所有边连容量为\(1\)的边,边向其两端的点连容量为\(inf\)的边,点向汇点\(T\)连容量为\(g\)的边
二分下界:\(\frac{1}{n}\),上界:\(m\),精度:\(\frac{1}{n^2}\)
\(code:\)
bool bfs()
{
for(int i=s;i<=t;++i) cur[i]=head[i];
memset(d,0,sizeof(d));
queue<int> q;
q.push(s);
d[s]=1;
while(!q.empty())
{
int x=q.front();
q.pop();
for(int i=head[x];i;i=e[i].nxt)
{
int y=e[i].to;
double v=e[i].v;
if(d[y]||fabs(v)<eps) continue;
d[y]=d[x]+1;
q.push(y);
}
}
return d[t];
}
double dfs(int x,double lim)
{
if(x==t) return lim;
double res=lim,flow;
for(int &i=cur[x];i;i=e[i].nxt)
{
int y=e[i].to;
double v=e[i].v;
if(d[y]!=d[x]+1||fabs(v)<eps) continue;
if(fabs(flow=dfs(y,min(res,v)))>=eps)
{
res-=flow;
e[i].v-=flow;
e[i^1].v+=flow;
if(fabs(res)<eps) break;
}
}
return lim-res;
}
double dinic()
{
double flow,ans=0;
while(bfs())
while(fabs(flow=dfs(s,inf))>=eps)
ans+=flow;
return ans;
}
double check(double x)
{
edge_cnt=1;
memset(head,0,sizeof(head));
for(int i=1;i<=n;++i) add(i+m,t,x);
for(int i=1;i<=m;++i)
{
int x=ed[i].x,y=ed[i].y;
add(s,i,1.0),add(i,x+m,inf),add(i,y+m,inf);
}
return m*1.0-dinic();
}
int work()
{
int ans=0;
memset(du,0,sizeof(du));
memset(vis,0,sizeof(vis));
check(g);
for(int i=1;i<=m;++i)
{
int x=ed[i].x,y=ed[i].y;
if(d[i])
{
if(++du[x]==1) ans++,vis[x]=true;
if(++du[y]==1) ans++,vis[y]=true;
}
}
return ans;
}
......
l=0,r=m,g=0;
while(l+1/((double)n*(double)n)<r)
{
double mid=(l+r)/2.0;
if(check(mid)>eps) g=l=mid;
else r=mid;
}
printf("%d\n",work());
for(int i=1;i<=n;++i)
if(vis[i])
printf("%d\n",i);
二元关系指如选和不选的关系
建立最小割模型,来解决一系列问题,如happiness、文理分科和人员雇佣
标签:work alt date() 流量 网络流 复杂 利用 oid 精度
原文地址:https://www.cnblogs.com/lhm-/p/12229508.html