Day 4

网络流的理论性知识(算了。。我自己都看不下去,还是整理些例题以后复习用吧qaq):

一、PPT(主要内容)

 

二、搜自度娘

定义

  ①图论中的一种理论与方法,研究网络上的一类最优化问题 。1955年 ,T.E.哈里斯在研究铁路最大通量时首先提出在一个给定的网络上寻求两点间最大运输量的问题。1956年,L.R. 福特和 D.R. 富尔克森等人给出了解决这类问题的算法,从而建立了网络流理论。所谓网络或容量网络指的是一个连通的赋权有向图 D= (V、E、C) , 其中V 是该图的顶点集,E是有向边(即弧)集,C是弧上的容量。此外顶点集中包括一个起点和一个终点。网络上的流就是由起点流向终点的可行流,这是定义在网络上的非负函数,它一方面受到容量的限制,另一方面除去起点和终点以外,在所有中途点要求保持流入量和流出量是平衡的。如果把下图看作一个公路网,顶点v1…v6表示6座城镇,每条边上的权数表示两城镇间的公路长度。现在要问 :若从起点v1将物资运送到终点v6去 ,应选择那条路线才能使总运输距离最短?这样一类问题称为最短路问题 。 如果把上图看作一个输油管道网 , v1 表示发送点,v6表示接收点,其他点表示中转站 ,各边的权数表示该段管道的最大输送量。现在要问怎样安排输油线路才能使从v1到v6的总运输量为最大。这样的问题称为最大流问题。

  ②最大流理论是由福特和富尔克森于 1956 年创立的 ,他们指出最大流的流值等于最小割(截集)的容量这个重要的事实,并根据这一原理设计了用标号法求最大流的方法,后来又有人加以改进,使得求解最大流的方法更加丰富和完善 。最大流问题的研究密切了图论和运筹学,特别是与线性规划的联系,开辟了图论应用的新途径。

  ③假设G(V,E) 是一个有限的有向图,它的每条边(u,v)∈E都有一个非负值实数的容量c(u,v) 。如果(u,v)不属于E,我们假设c(u,v) = 0 。我们区别两个顶点:一个源点s和一个汇点t。一道网络流是一个对于所有结点u和v都有以下特性的实数函数f:V×V→R

(1)容量限制

(2)f(u,v)≤c(u,v)一条边的流不能超过它的容量。

(3)斜对称

(4)f(u,v)=-f(v,u)由u到v的净流必须是由v到u的净流的相反(参考例子)。(既然要看网络流,这是一定要知道的)

(5)流守恒

(6)除非u=s或u=t,否则Σ(w∈V)f(u,w)=0 一结点的净流是零,除了"制造"流的源点和"消耗"流的汇点。

 

  ④注意f(u,v) 是由u到v的净流。如果该图代表一个实质的网络,由u到v有 4 单位的实际流及由v到u有 3 单位的实际流,那么f(u,v) = 1 及f(v,u) = − 1。

  ⑤边的剩余容量 (Residual Capacity)是cf(u,v) =c(u,v)−f(u,v)。这定义了以Gf(V,Ef)表示的剩余网络 (Residual Network),它显示可用的容量的多少。留意就算在原网络中由u到v没有边,在剩余网络乃可能有由u到v的边。因为相反方向的流抵消,减少由v到u的流等于增加由u到v的流。扩张路径 (Augmenting Path)是一条路径 (u1,u2...uk),而u1 =s、uk=t及cf(ui,ui+ 1) > 0,这表示沿这条路径传送更多流是可能的。

最大流算法

1、augment path,直译为"增广路径",其思想大致如下:

  ①原有网络为G,设有一辅助图G',其定义为V(G') = V(G),E(G')初始值(也就是容量)与E(G)相同。每次操作时从Source点搜索出一条到Sink点的路径,然后将该路径上所有的容量减去该路径上容量的最小值,然后对路径上每一条边<u,v>添加或扩大反方向的容量,大小就是刚才减去的容量。一直到没有路为止。此时辅助图上的正向流就是最大流。

  ②我们很容易觉得这个算法会陷入死循环,但事实上不是这样的。我们只需要注意到每次网络中由Source到Sink的流都增加了,若容量都是整数,则这个算法必然会结束。

  ③寻找通路的时候可以用DFS,BFS最短路等算法。就这两者来说,BFS要比DFS快得多,但是编码量也会相应上一个数量级。

  ④增广路方法可以解决最大流问题,然而它有一个不可避免的缺陷,就是在极端情况下每次只能将流扩大1(假设容量、流为整数),这样会造成性能上的很大问题,解决这个问题有一个复杂得多的算法,就是预推进算法。

2、push label,直译为"预推进"算法。

3、压入与重标记(Push-Relabel)算法

  除了用各种方法在剩余网络中不断找增广路(augmenting)的Ford-Fulkerson系的算法外,还有一种求最大流的算法被称为压入与重标记(Push-Relabel)算法。它的基本操作有:压入,作用于一条边,将边的始点的预流尽可能多的压向终点;重标记,作用于一个点,将它的高度(也就是label)设为所有邻接点的高度的最小值加一。Push-Relabel系的算法普遍要比Ford-Fulkerson系的算法快,但是缺点是相对难以理解。

  Relabel-to-Front使用一个链表保存溢出顶点,用Discharge操作不断使溢出顶点不再溢出。Discharge的操作过程是:若找不到可被压入的临边,则重标记,否则对临边压入,直至点不再溢出。算法的主过程是:首先将源点出发的所有边充满,然后将除源和汇外的所有顶点保存在一个链表里,从链表头开始进行Discharge,如果完成后顶点的高度有所增加,则将这个顶点置于链表的头部,对下一个顶点开始Discharge。

  Relabel-to-Front算法的时间复杂度是O(V^3),还有一个叫Highest Label Preflow Push的算法复杂度据说是O(V^2*E^0.5)。我研究了一下HLPP,感觉它和Relabel-to-Front本质上没有区别,因为Relabel-to-Front每次前移的都是高度最高的顶点,所以也相当于每次选择最高的标号进行更新。还有一个感觉也会很好实现的算法是使用队列维护溢出顶点,每次对pop出来的顶点discharge,出现了新的溢出顶点时入队。

  Push-Relabel类的算法有一个名为gap heuristic的优化,就是当存在一个整数0<k<V,没有任何顶点满足h[v]=k时,对所有h[v]>k的顶点v做更新,若它小于V+1就置为V+1。

最小费用流算法

一.Ford和Fulkerson迭加算法.

  基本思路:把各条弧上单位流量的费用看成某种长度,用求解最短路问题的方法确定一条自V1至Vn的最短路;在将这条最短路作为可扩充路,用求解最大流问题的方法将其上的流量增至最大可能值;而这条最短路上的流量增加后,其上各条弧的单位流量的费用要重新确定,如此多次迭代,最终得到最小费用最大流.

二.迭加算法:

  ①给定目标流量F或∞,给定最小费用的初始可行流=0

  ② 若V(f)=F,停止,f为最小费用流;否则转(3).

  ③ 构造 相应的新的费用有向图W(fij),在W(fij)寻找Vs到Vt的最小费用有向路P(最短路),沿P增加流f的流量直到F,转(2);若不存在从Vs到Vt的最小费用的有向路P,停止.f就是最小费用最大流.最大流问题仅注意网络流的流通能力,没有考虑流通的费用。实际上费用因素是很重要的。例如在交通运输问题中,往往要求在完成运输任务的前提下,寻求一个使总运输费用最省的运输方案,这就是最小费用流问题。如果只考虑单位货物的运输费用,那么这个问题就变成最短路问题。由此可见,最短路问题是最小费用流问题的基础。现已有一系列求最短路的成功方法。最小费用流(或最小费用最大流)问题 ,可以交替使用求解最大流和最短路两种方法,通过迭代得到解决。

三.二圈算法:

  ① 利用Ford和Fulkson标号算法找出流量为F(<=最大流)的流f.

  ②构造f对应的调整容量的流网络N'(f).

  ③ 搜索N'(f)中的负费用有向图C(Floyd算法),若没有则停止,否则转(4).

  ④在C上找出最大的循环流,并加到N上去,同时修改N'(F)中C的容量,转(3).

. ZKW费用流:

  ①费用流是网络流的一个很重要的组成部分,也是非常有用的一种图论模型,关于费用流的算法,流传比较广的主要是消圈和增广路算法,而近来炒得沸沸扬扬的ZKW算法也是一种非常优秀的算法,这里我就谈谈我对此算法的一些理解。

  ②此算法是由ZKW大牛创立,主要思想仍然是找增广路,只是有了一些优化在里边。原来我们找增广路主要是依靠最短路算法,如SPFA。因此此算法的时间复杂度主要就取决于增广的次数和每次增广的耗费。由于每一次找增广路是都是重新算一遍,这样似乎显得有些浪费,如果我们能够缩短找增广路的时间,那必定会大大地优化算法。

  ③值得注意的是,在寻求最短路得过程中,设dis[i]为i到起点的距离,对于每一条由i引向j的边,必有dis[j]<=dis[i]+map[i][j];既然满足这样的恒等式,我们就可以借用KM算法的调整思想来寻求最短路,每次只走dis[j]=dis[i]+map[i][j]的路径,一旦不存在到达终点的路径,就扫描每一条边,找到最小的距离增加值,使得有至少一条新边被加入相等子图。

     算法流程如下:

    1.将dis数组清零,表示当前的相等子图内只有起点。(如果存在负权边,必须要用spfa跑一遍,初始化出dis数组,下面的第二题就是这样的例子)。

    2.深搜,如果到达终点,全部回退更改流量,再进行步骤2;否则,转3。

    3.修改dis的值,如果无法修改,结束程序,已经找到的答案,反之转2。

    有上下界v上面讨论的网络流都只对每条弧都限定了上界(其实其下界可以看成0),现在给每条弧<Vi, Vj>加上一个下界限制Aij (即必须满足  Aij≤fij)。v弧上数字对第一个是上界,第二个是下界。若是撇开下界不看,此图的最大流如图(a)所示,流量是6;但若是加入了下界的限制,它的最大流量就只有5了。

、网络流的基本概念

例:现在想将一些物资从S运抵T,必须经过一些中转站。连接中转站的是公路,每条公路都有最大运载量。如下图:每条弧代表一条公路,弧上的数表示该公路的最大运载量。最多能将多少货物从S运抵T?

分析:

这是一个典型的网络流模型。若有向图G=(V,E)满足下列条件: 有且仅有一个顶点S,它的入度为零,即d-(S) = 0,这个顶点S便称为源点,或称为发点。有且仅有一个顶点T,它的出度为零,即d+(T) = 0,这个顶点T便称为汇点,或称为收点。③每一条弧都有非负数,叫做该边的容量。边(vi, vj)的容量用cij表示。则称之为网络流图,记为G = (V, E, C)

1.可行流

对于网络流图G,每一条弧(i,j)都给定一个非负数fij,这一组数满足下列三条件时称为这网络的可行流,用f表示它。(1) 每一条弧(i,j)有fij≤cij。(2) 除源点S和汇点T以外的所有的点vi,恒有:该等式说明中间点vi的流量守恒,输入与输出量相等。(3) 对于源点S和汇点T有:这里V(f)表示该可行流f的流量。

2.可改进路

给定一个可行流f=。若fij = cij,称<vi, vj>为饱和弧;否则称<vi, vj>为非饱和弧。若fij = 0,称<vi, vj>为零流弧;否则称<vi, vj>为非零流弧。定义一条道路P,起点是S、终点是T。把P上所有与P方向一致的弧定义为正向弧,正向弧的全体记为P+;把P上所有与P方向相悖的弧定义为反向弧,反向弧的全体记为P-。给定一个可行流f,P是从S到T的一条道路,如果满足:那么就称P是f的一条可改进路。(有些书上又称:可增广轨)之所以称作"可改进",是因为可改进路上弧的流量通过一定的规则修改,可以令整个流量放大。具体方法下一节会重点介绍,此不赘述。

定理:对于已知的网络流图,设任意一可行流为f,任意一割切为(U, W),必有:V(f) ≤ C(U, W)。通俗简明的讲:"最大流小于等于任意割"。这是"流理论"里最基础最重要的定理。整个"流"的理论系统都是在这个定理上建立起来的,必须特别重视。

、求最大流

对可改进路P上的弧<vi, vj>,分为两种情况讨论:

  ①第一种情况:<vi, vj>∈P+,可以令fij增加一个常数delta。必须满足fij + delta ≤ cij,即delta ≤ cij – fij。

  ②第二种情况:<vi, vj>∈P-,可以令fij减少一个常数delta。必须满足fij - delta ≥ 0,即delta ≤ fij

  根据以上分析可以得出delta的计算公式:因为P+的每条弧都是非饱和弧,P-的每条弧都是非零流弧,所以delta > 0。容易证明,按照如此规则修正流量,既可以使所有中间点都满足"流量守恒"(即输入量等于输出量),又可以使得总的流量有所增加(因为delta > 0)。

  因此我们对于任意的可行流f,只要在f中能找到可改进路,那么必然可以将f改造成为流量更大的一个可行流。我们要求的是最大流,现在的问题是:倘若在f中找不到可改进路,f就一定是最大流

定理1 可行流f是最大流的充分必要条件是:f中不存在可改进路。

证明:

  首先证明必要性:已知最大流f,求证f中不存在可改进路。若最大流f中存在可改进路P,那么可以根据一定规则(详见上文)修改P中弧的流量。可以将f的流量放大,这与f是最大流矛盾。故必要性得证。再证明充分性:已知流f,并且f中不存在可改进路,求证f是最大流。我们定义顶点集合U, W如下:

  (a) S∈U,

  (b) 若x∈U,且fxy<cxy,则y∈U;若x∈U,且fyx>0,则y∈U。(这实际上就是可改进路的构造规则)

  (c) W = V \ U。由于f中不存在可改进路,所以T∈W;又S∈U,所以U、W是一个割切(U, W)。按照U的定义,若x∈U,y∈W,则fxy = cxy。若x∈W,y∈U,则fxy = 0。又因 v(f)≤C(U,W)所以f是最大流。得证。

重要定理:最大流最小割定理:最大流等于最小割,即max V(f) = min C(U, W)。

求最大流的算法:

step 1. 令所有弧的流量为0,从而构造一个流量为0的可行流f(称作零流)。

step 2. 若f中找不到可改进路则转step 5;否则找到任意一条可改进路P。

step 3. 根据P求delta。

step 4. 以delta为改进量,更新可行流f。转step 2。

step 5. 算法结束。此时的f即为最大流。

、最小费用最大流

1.问题的模型

流最重要的应用是尽可能多的分流物资,这也就是我们已经研究过的最大流问题。然而实际生活中,最大配置方案肯定不止一种,一旦有了选择的余地,费用的因素就自然参与到决策中来。

一般的,设有带费用的网络流图G = (V, E, C, W),每条弧<Vi, Vj>对应两个非负整数Cij、Wij,表示该弧的容量和费用。若流f满足:

(a) 流量V(f)最大。

(b) 满足a的前提下,流的费用Cost(f) = 最小。

就称f是网络流图G的最小费用最大流。

八、例题:

T1(P2756 飞行员配对方案问题):

 #include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#define inf 0x3f3f3f3f
using namespace std; int n,m,s,t;
struct node {
int v,next,cap;
} e[];
int head[],tot=;
int dis[]; void add_node(int a,int b,int c) {
e[++tot]=(node) {b,head[a],c};
head[a]=tot;
} bool bfs() {
memset(dis,-,sizeof(dis));
queue<int> q;
q.push(s);
dis[s]=;
while(!q.empty()) {
int x=q.front();
q.pop();
for(int i=head[x]; i; i=e[i].next) {
int v=e[i].v;
if(e[i].cap&&dis[v]==-) {
dis[v]=dis[x]+;
q.push(v);
}
}
}
return dis[t]!=-;
} int dfs(int now,int f) {
if(now==t) return f;
int rest=f;
for(int i=head[now]; i; i=e[i].next) {
int v=e[i].v;
if(e[i].cap>&&rest>&&dis[v]==dis[now]+) {
int t=dfs(v,min(rest,e[i].cap));
if(!t) dis[v]=;
e[i].cap-=t;
e[i^].cap+=t;
rest-=t;
}
}
return f-rest;
} void dinic() {
int ans=;
while(bfs()) ans+=dfs(s,inf);
if(!ans) {
printf("No Solution!");
return;
}
printf("%d\n",ans);
for(int i=;i<=tot;i+=)
{
int v=e[i].v;
int v1=e[i^].v;
if(v==s||v1==s||v==t||v1==t) break;
if(e[i^].cap!=)
{
printf("%d %d\n",e[i^].v,e[i].v);
}
}
} int main() {
scanf("%d%d",&m,&n);
int x,y;
while(scanf("%d%d",&x,&y)==) {
if(x==-&&y==-) break;
add_node(x,y,);
add_node(y,x,);
}
s=;t=n+;
for(int i=; i<=m; ++i) {
add_node(s,i,);
add_node(i,s,);
}
for(int i=m+; i<=n; ++i) {
add_node(i,t,);
add_node(t,i,);
}
dinic();
}

代码实现

T2(P2317 [HNOI2005]星际贸易):

 #include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;
const int INF=0x3f3f3f3f;
typedef long long ll;
const int N=;
const int M=; int dp[N][N],f[N][M],q[M][N];
int n,m,s,t; struct node {
int a,b,l,f,p;
}e[N*]; struct edge {
int h,z,c;
}a[M*]; inline int read() {
int n=,f=;char ch=getchar();
while (ch<''||ch>'') {if(ch=='-') f=-;ch=getchar();}
while (ch<='' && ch>='') {n=(n<<)+(n<<)+ch-'';ch=getchar();}
return n*f;
} inline void work_(int i,int j) {
if(e[i].p> && j>)
f[i][j]=min(f[i][j],f[i][j-]+e[i].p);
if(a[j+].z>a[j+].h)
f[i][j]=min(f[i][j],f[q[j+][a[j+].h]][j+]+e[i].f);
if(a[i].c) a[j].h=a[j].z=;
while(a[j].z>a[j].h && f[q[j][a[j].z-]][j]>=f[i][j]) a[j].z--;
q[j][a[j].z++]=i;
while(a[j].h<a[j].z && e[i+].l-e[q[j][a[j].h]].l>t) a[j].h++;
} inline void init() {
n=read(),m=read(),s=read(),t=read();
if(s>*n) s=*n;
for(int i=;i<=n;++i)
e[i].a=read(),e[i].b=read(),e[i].l=read(),e[i].p=read(),e[i].f=read();
} int main(){
init();
for(int i=;i<=n;i++)
if(e[i].l-e[i-].l>t) {
printf("Poor Coke!");
return ;
}
memset(dp,-,sizeof(dp));
dp[][]=;
for(int i=;i<=n;++i)
for(int j=;j<=m;++j){
if(dp[i-][j]>=) dp[i][j]=dp[i-][j];
if(j>=e[i].a && dp[i-][j-e[i].a]>=)
dp[i][j]=max(dp[i][j],dp[i-][j-e[i].a]+e[i].b);
}
int v=;
for(int i=;i<=m;i++)
if(dp[n][i]>dp[n][v]) v=i;
for(int i=n,j=v;i>=;i--)
if(dp[i][j]==dp[i-][j]) continue;
else a[i].c=true,j-=e[i].a;
memset(f,0x3f,sizeof(f));
f[][s]=;
q[s][a[s].z++]=;
for(int i=;i<=n;i++)
for(int j=;j<=s;j++) work_(i,j);
int u=;
for(int i=;i<=s;i++)
if(f[n][i]<f[n][u]) u=i;
if(f[n][u]==INF) printf("Poor Coke!");
else printf("%d %d", dp[n][v], dp[n][v]-f[n][u]);
return ;
}
/*
6 3 10 4
1 2 1 1 1
1 2 2 2 1
1 2 3 9 1
1 1 4 0 1
1 1 5 0 1
1 1 6 1 1 6 2
*/

代码实现

T3(P3324 [SDOI2015]星际战争):

 #include<iostream>
#include<cstdio>
#include<cmath>
#include<queue>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=;
int n,m,js;
int a[N],b[N],f[N][N],head[N],q[N];
ll sum; struct node{
int to,nxt;
ll w;
}e[N*];//改 inline ll read() {
ll n=,f=;char ch=getchar();
while (ch<''||ch>'') {if(ch=='-') f=-;ch=getchar();}
while (ch<=''&&ch>='') {n=n*+ch-'';ch=getchar();}
return n*f;
} inline void add_(int x,int y,ll z) {
e[js].to=y; e[js].w=z;
e[js].nxt=head[x]; head[x]=js++;//js++,改(必须改)
e[js].to=x; e[js].w=;
e[js].nxt=head[y]; head[y]=js++;
} inline int bfs(int s,int t) {
memset(q,,sizeof(q));
queue <int > p;
q[s]=;
p.push(s);
while (!p.empty()) {
int u=p.front();
p.pop();//改
for(int i=head[u];i!=-;i=e[i].nxt) {//改,循环是i!=-1
if(e[i].w> && !q[e[i].to]) {
q[e[i].to]=q[u]+;
p.push(e[i].to);
}
}
}
return q[t];
} inline ll dfs(int v,int t,ll f) {//改,dfs最好不用inline
if(v==t) return f;
ll dz=;
for(int i=head[v];i!=-&&dz<f;i=e[i].nxt) {//改
if(e[i].w> && q[v]+==q[e[i].to]) {
ll ans=dfs(e[i].to,t,min(e[i].w,f-dz));
if(ans>) {
e[i].w-=ans;
e[i^].w+=ans;
dz+=ans;
} else {
q[e[i].to]=-;
}
}
}
return dz;
} inline ll dinic(int s,int t) {
ll flow=;
while (bfs(s,t)) {
flow+=dfs(s,t,0x3f3f3f3f);
}
return flow;
} bool check(ll t) {
memset(head,-,sizeof(head)),js=;
for(int i=;i<=m;++i) add_(,i,t*b[i]);
for(int i=;i<=n;++i) add_(i+m,n+m+,a[i]*);
for(int i=;i<=m;++i)
for(int j=;j<=n;++j)
if(f[i][j]) add_(i,j+m,0x3f3f3f3f);//少了一个3f
return dinic(,n+m+)==sum;
} int main() {
n=read(),m=read();
for(int i=;i<=n;++i) {
a[i]=read();
sum+=a[i]*;
}
for(int i=;i<=m;++i) b[i]=read();
for(int i=;i<=m;++i)
for(int j=;j<=n;++j) f[i][j]=read();
int l=,r=;
while (l<r) {
int mid=(l+r)>>;
if (check(mid)) r=mid;
else l=mid+;
}
printf("%.6f\n", r/10000.0);
return ;
}

代码实现

T4(P2472 [SCOI2007]蜥蜴):

 #include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
using namespace std;
const int inf=0x3f3f3f3f;
const int N=;
const int M=; int r,c,d,S,t,js=,ans;
int b[N],f[M][M],head[N],dis[N],q[N*];
char s[M]; struct node {
int v,nxt,cap;
}e[N*]; inline int read() {
int n=,f=;char ch=getchar();
while(ch<''||ch>''){if(ch=='-') f=-;ch=getchar();}
while(ch>=''&&ch<=''){n=n*+ch-'';ch=getchar();}
return n*f;
} inline void add(int x,int y,int cap) {
e[++js].v=y; e[js].cap=cap;
e[js].nxt=head[x]; head[x]=js;
e[++js].v=x; e[js].cap=;
e[js].nxt=head[y]; head[y]=js;
} inline int sqr(int x) {
return x*x;
} inline double calc(int a,int b,int c,int d) {
return sqrt(sqr(a-c)+sqr(b-d));
} inline int pos(int x,int y,int add) {
return (x-)*c+y+r*c*add;
} inline bool pd(int x,int y) {
return x<=d||r-x<d||y<=d||c-y<d;
} inline bool bfs() {
for(int i=S;i<=t;++i) dis[i]=inf;
int h=,k=;
q[k]=S,dis[S]=;
while(h!=k){
int x=q[++h];
for(int i=head[x];i;i=e[i].nxt){
int v=e[i].v;
if(e[i].cap && dis[v]>dis[x]+){
dis[v]=dis[x]+;
if(v==t) return ;
q[++k]=v;
}
}
}
return dis[t]<inf;
} inline int dfs(int x,int f){
if(x==t) return f;
int res=f;
for(int i=head[x];i;i=e[i].nxt){
int v=e[i].v;
if(e[i].cap && dis[v]==dis[x]+){
int t=dfs(v,min(res,e[i].cap));
if(!t) dis[v]=;
e[i].cap-=t;e[i^].cap+=t;
res-=t;
}
}
return f-res;
} inline int dinic() {
int res=;
while(bfs()) {
for(int i=S;i<=t;++i) b[i]=head[i];
res+=dfs(S,inf);
}
return ans-res;
} int main() {
r=read(),c=read(),d=read();
S=,t=*r*c+;
for(int i=;i<=r;++i) {
scanf("%s",s+);
for(int j=;j<=c;++j) f[i][j]=s[j]-'';
}
for(int i=;i<=r;++i) {
scanf("%s",s+);
for(int j=;j<=c;++j)
if(s[j]=='L') ans++,add(S,pos(i,j,),);
}
for(int i=;i<=r;++i)
for(int j=;j<=c;++j)
if(f[i][j]) add(pos(i,j,),pos(i,j,),f[i][j]);
for(int i=;i<=r;i++) {
for(int j=;j<=c;++j) {
if(!f[i][j]) continue;
for(int z=;z<=r;z++) {
for(int o=;o<=c;o++) {
if(i==z && j==o) continue;
if(f[z][o] && calc(i,j,z,o)<=d)
add(pos(i,j,),pos(z,o,),inf),add(pos(z,o,),pos(i,j,),inf);
}
}
}
}
for(int i=;i<=r;++i)
for(int j=;j<=c;++j)
if(pd(i,j)) add(pos(i,j,),t,inf);
printf("%d",dinic());
return ;
}

代码实现

T5(P4001 [BJOI2006]狼抓兔子):

 #include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
const int inf=0x3f3f3f3f;
const int N=2e6+; int n,m,s,t,res,ans,js=;
int head[N],dis[N]; struct node {
int v,nxt,cap;
}e[N*]; inline int read() {
int n=,f=;char ch=getchar();
while (ch<'' || ch>'') {if(ch=='-') f=-;ch=getchar();}
while (ch<='' && ch>='') {n=(n<<)+(n<<)+ch-'';ch=getchar();}
return n*f;
} inline void add(int a,int b,int z) {
e[++js].v=b,e[js].nxt=head[a];
e[js].cap=z,head[a]=js;
} inline int bfs() {
memset(dis,-,sizeof(dis));
queue<int>q;
q.push(s);
dis[s]=;
while (!q.empty()) {
int u=q.front();
q.pop();
for(int i=head[u];i;i=e[i].nxt) {
int v=e[i].v;
if(dis[v]==- && e[i].cap) {
dis[v]=dis[u]+;
q.push(v);
}
}
}
return dis[t]!=-;
} inline int dfs(int p,int f) {
if(p==t) return f;
int w=f;
for(int i=head[p];i;i=e[i].nxt) {
int v=e[i].v;
if(w> && dis[v]==dis[p]+ && e[i].cap>) {
int t=dfs(v,min(w,e[i].cap));
if(!t) dis[v]=;
e[i].cap-=t,e[i^].cap+=t,w-=t;
}
}
return f-w;
} inline int dinic() {
int ans=;
while(bfs()) ans+=dfs(s,inf);
return ans;
} int main() {
n=read(),m=read();
s=,t=n*m;
int x;
for(int i=;i<=n;++i) {
int jc=i*m-m;
for(int j=;j<m;++j) {
x=read();
add(jc+j,jc+j+,x),add(jc+j+,jc+j,x);
}
}
for(int i=;i<n;++i) {
int jc=i*m-m;
for(int j=;j<=m;++j) {
x=read();
add(jc+j,jc+j+m,x),add(jc+j+m,jc+j,x);
}
}
for(int i=;i<n;++i) {
int jc=i*m-m;
for(int j=;j<m;++j) {
x=read();
add(jc+j,jc+m+j+,x),add(jc+m++j,jc+j,x);
}
}
res=dinic();
printf("%d",res);
return ;
}

代码实现

T6(P2774 方格取数问题):

 #include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=;
const int NN=;
const int maxn=;
const int maxm=; int n,m,s,t,x,y,js=,l,r,ax;
int a[N][N],head[NN],h[NN],q[NN];
int fx[][]={{-,},{,-},{,},{,}};
ll sum=; struct node{
int to,w,nxt;
}e[NN]; inline int read() {
int n=,f=;char ch=getchar();
while (ch<'' || ch>'') {if(ch=='-') f=-;ch=getchar();}
while (ch<='' && ch>='') {n=(n<<)+(n<<)+ch-'';ch=getchar();}
return n*f;
} inline int calc(int x,int y) {
return (x-)*m+y;
} inline void add_edge(int x,int y,int z) {
e[++js].to=y;
e[js].w=z;
e[js].nxt=head[x];
head[x]=js;
} inline int bfs() {
memset(h,-,sizeof(h));
h[s]=,l=,r=,q[r]=s;
while (l!=r) {
l++;
if(l==maxn) l=;
int u=q[l];
for(int i=head[u];i;i=e[i].nxt) {
int v=e[i].to;
if (h[v]==- && e[i].w>) {
h[v]=h[u]+,r++;
if(r==maxn) r=;
q[r]=v;
}
}
}
return h[t]!=-;
} inline int dfs(int u,int s) {
if(u==t || s==) return s;
int ansx=,v,W;
for(int i=head[u];i;i=e[i].nxt) {
if(h[e[i].to]==h[u]+) {
v=e[i].to;
W=dfs(v,min(e[i].w,s-ansx));
ansx+=W,e[i].w-=W,e[i^].w+=W;
if(ansx==s) return s;
}
}
if(ansx==) h[u]=-;
return ansx;
} inline void work_(int i,int j) {
for(int k=;k<;k++) {
x=i+fx[k][],y=j+fx[k][];
if(x<= || y<= || x>n || y>m) continue;
add_edge((i-)*m+j,(x-)*m+y,1e8),add_edge((x-)*m+y,(i-)*m+j,);
}
} inline int init() {
n=read(),m=read();
s=n*m+,t=n*m+;
for(int i=;i<=n;++i) {
for(int j=;j<=m;++j) {
ax=read();
sum+=ax;
if((i+j)%==) add_edge(s,(i-)*m+j,ax),add_edge((i-)*m+j,s,);
else add_edge((i-)*m+j,t,ax),add_edge(t,(i-)*m+j,);
}
}
} int main() {
init();
for(int i=;i<=n;i++)
for(int j=;j<=m;j++)
if((i+j)%==) work_(i,j);
while (bfs()) sum-=dfs(s,1e9);
printf("%lld\n",sum);
return ;
}
/*
题目描述
在一个有 m*n 个方格的棋盘中,每个方格
中有一个正整数。现要从方格中取数,使任
意 2 个数所在方格没有公共边,且取出的数
的总和最大。试设计一个满足要求的取数算法
。对于给定的方格棋盘,按照取数要求编程找
出总和最大的数。 输入格式:
第 1 行有 2 个正整数 m 和 n,分别表示棋盘的行数和列数。
接下来的 m 行,每行有 n 个正整数,表示棋盘方格中的数。 输出格式:
程序运行结束时,将取数的最大总和输出 输入样例1:
3 3
1 2 3
3 2 3
2 3 1 输出样例1: 复制
11 */

代码实现

T7(P3305 [SDOI2013]费用流):

 #include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
const int N=1e5+;
int js=,n,m,p,s,t,ans,x,y,z;
int head[N],dis[N],q[N],vis[N];
double L,R; struct edge {
int v,nxt;
double cap;
}e[N<<]; struct node {
int x,y;
double z;
}d[N]; inline int read() {
int n=,f=;char ch=getchar();
while(ch<''||ch>''){if(ch=='-') f=-;ch=getchar();}
while(ch>=''&&ch<=''){n=n*+ch-'';ch=getchar();}
return n*f;
} inline void add(int x,int y,double z) {
e[++js].v=y; e[js].cap=z;
e[js].nxt=head[x]; head[x]=js;
e[++js].v=x; e[js].cap=;
e[js].nxt=head[y]; head[y]=js;
} inline bool bfs() {
for(int i=s;i<=t;i++) dis[i]=-,vis[i]=;
int h=,k=;
q[k]=s,dis[s]=;
while(h!=k) {
int u=q[++h];
for(int i=head[u];i;i=e[i].nxt) {
if(dis[e[i].v]==- && e[i].cap) {
dis[e[i].v]=dis[u]+;
if(e[i].v==t) return ;
q[++k]=e[i].v;
}
}
}
return ;
} inline double dfs(int x,double f) {
if(x==t) return f;
double res=,t;
for(int i=head[x];i;i=e[i].nxt) {
if(e[i].cap && dis[e[i].v]==dis[x]+) {
t=dfs(e[i].v,min(e[i].cap,f));
e[i].cap-=t,e[i^].cap+=t;
res+=t,f-=t;
if(!f) return res;
}
}
if(!res) dis[x]=-;
return res;
} inline int dinic() {
double res=;
while(bfs()) res+=dfs(s,2e9);
return res;
} inline bool check(double lim) {
memset(head,,sizeof head);js=;
for(int i=;i<=m;i++) add(d[i].x,d[i].y,min(d[i].z,lim));
return dinic()==ans;
} int main() {
n=read(),m=read(),p=read();
s=,t=n,R=;
for(int i=;i<=m;i++) {
d[i].x=read(),d[i].y=read(),d[i].z=read();
add(d[i].x,d[i].y,d[i].z);
R=max(R,d[i].z);
}
ans=dinic();
double l=0.0,r=R,mid;
while(r-l>1e-) {
mid=(l+r)/2.0;
if(check(mid)) r=mid;
else l=mid;
}
printf("%d\n%.5lf\n",ans,1.0*p*mid);
return ;
}

代码实现

cyyz: Day 4 网络流整理的更多相关文章

  1. cyyz : Day 1 数论整理

    声明:感谢修改这篇博客的dsr Day 1 先说一下上午的听课吧,哎~,简直了,简直(⊙o⊙)…咋说呢,引人入胜???No! 是昏昏欲睡好吧...一点听课欲都没有(强撑....),一上午停下来简直怀疑 ...

  2. cyyz: Day 6 平衡树整理

    一.平衡树 知识点: ,并且左右两个子树都是一棵平衡二叉树.平衡二叉树的常用实现方法有红黑树.AVL.替罪羊树.Treap.伸展树等. 最小二叉平衡树的节点的公式如下 F(n)=F(n-1)+F(n- ...

  3. cyyz: Day 2 线段树知识整理

    Day 2 上午的听课,哎~昏昏欲睡好吧.. 一.扫描线 知识点: 由于多边形千变万化,要想填充多边形内部的所有像素,需要找到一种合适的规则,能够沿着一个方向,一个像素不漏地把多边形内部填满,同时不污 ...

  4. Android数据库相关整理

    今天对Android中数据库相关的操作和代码做了一个整理,便于自己之后的查阅.主要内容有: 1.原生数据库写法 2.终端进sqlite的操作 3.第三方库 4.事务处理 5.权限和路径 一.原生数据库 ...

  5. bzoj3504: [Cqoi2014]危桥 网络流

    一种网络流建图的思路吧,改天最好整理一波网络流建图思路 #include <bits/stdc++.h> using namespace std; int n,h,t,a1,a2,an,b ...

  6. ACM/IOI 历年国家集训队论文集和论文算法分类整理

    国家集训队1999论文集 陈宏:<数据结构的选择与算法效率--从IOI98试题PICTURE谈起> 来煜坤:<把握本质,灵活运用--动态规划的深入探讨> 齐鑫:<搜索方法 ...

  7. python 基础部分重点复习整理--从意识那天开始进阶--已结

    pythonic 风格编码 入门python好博客 进阶大纲 有趣的灵魂 老齐的教程 老齐还整理了很多精华 听说 fluent python + pro python 这两本书还不错! 元组三种遍历, ...

  8. 网络流$1$·简单的$EK$与$Dinic~of~Net-work ~ Flow$学习笔记

    \(2333\)这是好久之前学的了,不过一直在咕咕咕咕. 一般来讲,正常的网络流笔记一开始都是要给网络流图下定义的.那么我们不妨也来先进行一波这种操作. 那么网络流图,类似于有向图,边上带权,但是这个 ...

  9. P2762 太空飞行计划问题(网络流24题之一)

    题目描述 W 教授正在为国家航天中心计划一系列的太空飞行.每次太空飞行可进行一系列商业性实验而获取利润.现已确定了一个可供选择的实验集合E={E1,E2,…,Em},和进行这些实验需要使用的全部仪器的 ...

随机推荐

  1. 原!linux comm命令文件 比较 输出交集,差集。

    文件内容大致如下: 112805|300011222483|OL海12卓|47397c33e36cdbed26637c50dd305973|2019-08-06 10:50:13|登B录123|suc ...

  2. Quartz基础调度框架-第一篇控制台

    Quartz基础调度框架 Quartz核心的概念:scheduler任务调度.Job任务.Trigger触发器.JobDetail任务细节 结构 Conf 在这个基本结构里 是用来存放配置 publi ...

  3. 【WEB基础】HTML & CSS 基础入门(1)初识

    前面 我们每天都在浏览着网络上丰富多彩的页面,那么在网页中所呈现出的绚丽多彩的内容是怎么设计出来的呢?我们想要自己设计一个页面又该如何来做呢?对于刚刚接触网页设计的小伙伴来说,看到网页背后的一堆符号和 ...

  4. java中四种权限修饰符区别

    总的概括:public > protected > (default) > private 细分见下表格: 权限修饰符 public protected (default) priv ...

  5. Java自学-类和对象 引用

    什么是Java中的引用? 引用的概念,如果一个变量的类型是 类类型,而非基本类型,那么该变量又叫做引用. 步骤 1 : 引用和指向 new Hero(); 代表创建了一个Hero对象 但是也仅仅是创建 ...

  6. 科普帖:Linux操作系统

    使用计算机必然会接触操作系统,现代操作系统已经发展的十分成熟,一般用户都可以很轻松的使用计算机.然而,对于要利用计算机进行专业开发和应用的用户来说,需要更加深入地理解操作系统的原理和运行机制,这样才能 ...

  7. JDBC模糊查询的4种方式

    1:%放在占位符中              parameters.add("%"+familyMemberQueryBean.getFullName()+"%" ...

  8. jquery实现弹出层完美居中效果

    代码如下: showDiv($("#pop"));function showDiv(obj){ $(obj).show(); center(obj); $(window).scro ...

  9. django 自定义身份认证

    自定义身份认证: Django 自带的认证系统足够应付大多数情况,但你或许不打算使用现成的认证系统.定制自己的项目的权限系统需要了解哪些一些关键点,即Django中哪些部分是能够扩展或替换的.这个文档 ...

  10. 珠宝juelrye宝石

    juelrye n.珠宝 late 14c., juelrye "precious ornaments, jewel juelrye (uncountable) Adornment with ...