题目大意:
每当下雨的时候John农场里的一块三叶草园就会被淹成池塘,等水退去后可能需要很长的一段时间三叶草才能重新长出来,贝西很郁闷,因此John着手建造了一个排水系统,这样就可以让贝西的三叶草园再也都不会被淹没了,系统会将水引至附近的一条溪流。作为一名王牌工程师,John在每一条沟渠的起始位置安装了流量校准器可以控制沟渠的最大流速,John不仅对每条沟渠的限速非常了解,并且对整个系统的复杂布局也了如指掌,整个排水系统是一个复杂的网络结构,水会从池塘排出去,沟渠之间可能相互渗透并最终流入溪流中,所有沟渠都是单向的,可能存在环(水绕着圈循环流)。
现有多个测例(测例数无上限),在每个测例中会给出N个点(2 ≤ N ≤ 200),M条沟渠(0 ≤ M ≤ 200),对于每条沟渠都会给出起点和终点的编号(其中1表示池塘,N表示溪流)以及最大流量C(0 ≤ C ≤ 10,000,000),对于每个测例求出最大流量(流入溪流中的)。
注释代码:
/*
* Problem ID : POJ 1273 Drainage Ditches
* Author : Lirx.t.Una
* Language : C++
* Run Time : 0 ms
* Run Memory : 180 KB
*/
#include <string.h>
#include <stdio.h>
//每条沟渠的最大流速10,000,000
#define INF 10000001
//maximum number of nodes
//结点的最大数量为200
//下标取1-200
//1为源点,200为汇点
#define MAXNODEN 201
//maximum number of ditches
//沟渠的最大数量
//测例中给出的最大数量为200
//但是计算残余网络的时候需要计算反向弧
//因此需要乘以2
#define MAXDITCHN 400
#define MIN(x,y) ( (x) < (y) ? (x) : (y) )
typedef struct {//流结构体,用此构造残余网络
int v;//终点
int c;//carry,剩余流量
} Flow;
//传统的头插发构造邻接表
Flow flw[MAXDITCHN];//边表
int next[MAXDITCHN];//相同起点的边的下一条边
int head[MAXNODEN];
int e;
//augmentation path,增广路径
//aug[u]表示当前搜索出来的增广路径中
//u结点为起点的弧(即该流在边表中的编号)
//此弧当然是在搜索出来的当前增光路径中的
int aug[MAXNODEN];
//previous node
//在增广路径中当前结点的前驱结点
int pre[MAXNODEN];
//level
//表示在增广路径中个结点的层级
//这里表示路径标号,源点的标号最大
//汇点的标号最小
int lev[MAXNODEN];
//sum_lev[ levx ]表示层级为levx的结点的数量
//用以GAP优化
//由于是自底向上递推出各点的层级,因此如果
//当上一层结点数不为0但下一层结点数为0时表示
//已无法构造成完整的层级了,因此也不存在增广路径了
//标志着SAP算法的终结
int sum_lev[MAXNODEN];
void
addflw( int u, int v, int c ) {//add flow,添加边
flw[e].v = v;
flw[e].c = c;
next[e] = head[u];
head[u] = e++;
flw[e].v = u;
flw[e].c = 0;
next[e] = head[v];
head[v] = e++;
}
int
sap( int src, int des ) {//SAP算法
int u, v;//临时变量,表示起点和终点
int ans;
int f;//flow,表示当前增广路径中以当前结点为起始结点的弧在边表中的编号
//minimum level
//当某点无法增广时需重新计算其层级
//此时需要选出层级最小的子结点来更新其层级(最小子结点层级 + 1)
int minlev;
//minimum flow,计算当前增广路径中的最大可行流量(即路径中流量最小的边)
int minf;
int minu;//最小流量边的起始点
memset(lev + 1, 0, des * sizeof(int));//将所有点层级都初始化为0
sum_lev[0] = des;//因此层级为0的结点数量为des个,即n个
memset(sum_lev + 1, 0, des * sizeof(int));//将其余层级的数量都初始化为0
for ( u = 1; u <= des; u++ )//每个点的当前增广弧都初始化为边表中的第一条弧
aug[u] = head[u];
u = src;//通过改变u的值从源点向深处增光,因此这样初始化
ans = 0;
while ( lev[src] < des ) {//当源点的层级等于点的数量时(即层级的最大值)
//表示所有点都已经标号完毕,整个网络的层级已经完备,无需在构造
//因此表示已无更多的增广路可循,标志着算法的结束
if ( u == des ) {//当u探寻到汇点,表示已经找出一条完整的增广路了
for ( minf = INF, u = src; u != des; u = flw[f].v )
if ( f = aug[u], flw[f].c < minf ) {
minf = flw[f].c;
minu = u;
}//先获得当前增广路中最小弧的流量以及其起点
for ( u = src; u != des; u = flw[f].v ) {
f = aug[u];
flw[f].c -= minf;
flw[f ^ 1].c += minf;
}//对该增广路进行残余网络更新
ans += minf;//将可行流累加进最终结果
u = minu;//并记录最小弧的起点
}
//由于最小弧减去的是自己的流量,因此从该弧起点开始的
//新的增广路径被断开了,需要从这里衔接上
//从该结点开始寻找可增光的子结点
for ( f = aug[u]; f != -1; f = next[f] )
if ( flw[f].c && lev[u] == lev[ flw[f].v ] + 1 )
//注意上级结点和下级结点之间的关系
//不仅存在路径,并且上级结点比下级结点的层级(标号)大1
break;
if ( f != -1 ) {//可增广时将u纵深
v = flw[f].v;
aug[u] = f;//记录纵深的路径
pre[v] = u;
u = v;
}
else {//若不可增光,则需更新u的层级并回溯
//由于u的层级改变,导致u和u的前驱结点之间
//不满足“层级加1”的关系,因此需要回溯到u的前驱结点
//为u的前驱结点重新挑选可行的增广子结点
//GAP优化
//由于u不可增广,因此u层级需要改变
//这也意味着等于u原来层级的结点数量将减少一个
//但如果该层级的结点一个都没有了
//(但上一层级的结点仍然存在,这是显然的,因为只有上一层级结点存在才能增广出u)
//此时就代表已经出现断层,表示层级网络已经走投无路了,标志着算法的终结
if ( !( --sum_lev[ lev[u] ] ) )
break;
//否则更新路径
aug[u] = head[u];
//找出层级最小的u的可增广子结点
for ( minlev = des, f = head[u]; f != -1; f = next[f] )
if ( flw[f].c )
minlev = MIN( minlev, lev[ flw[f].v ] );
//利用该子结点的层级更新u的层级
lev[u] = minlev + 1;
sum_lev[ lev[u] ]++;//更新后新层级的结点数加1
if ( u != src )//并回溯,如果u已经是源点了则无法回溯
u = pre[u];
}
}
return ans;
}
int
main() {
int n, m;//点数和边数
int u, v, c;
while ( ~scanf("%d%d", &m, &n) ) {
memset( head + 1, -1, n * sizeof(int));
e = 0;
while ( m-- ) {
scanf("%d%d%d", &u, &v, &c);
addflw( u, v, c );
}
printf("%d\n", sap( 1, n ));
}
return 0;
}
无注释代码:
#include <string.h>
#include <stdio.h>
#define INF 10000001
#define MAXNODEN 201
#define MAXDITCHN 400
#define MIN(x,y) ( (x) < (y) ? (x) : (y) )
typedef struct {
int v;
int c;
} Flow;
Flow flw[MAXDITCHN];
int next[MAXDITCHN];
int head[MAXNODEN];
int e;
int aug[MAXNODEN];
int pre[MAXNODEN];
int lev[MAXNODEN];
int sum_lev[MAXNODEN];
void
addflw( int u, int v, int c ) {
flw[e].v = v;
flw[e].c = c;
next[e] = head[u];
head[u] = e++;
flw[e].v = u;
flw[e].c = 0;
next[e] = head[v];
head[v] = e++;
}
int
sap( int src, int des ) {
int u, v;
int ans;
int f;
int minlev;
int minf;
int minu;
memset(lev + 1, 0, des * sizeof(int));
sum_lev[0] = des;
memset(sum_lev + 1, 0, des * sizeof(int));
for ( u = 1; u <= des; u++ )
aug[u] = head[u];
u = src;
ans = 0;
while ( lev[src] < des ) {
if ( u == des ) {
for ( minf = INF, u = src; u != des; u = flw[f].v )
if ( f = aug[u], flw[f].c < minf ) {
minf = flw[f].c;
minu = u;
}
for ( u = src; u != des; u = flw[f].v ) {
f = aug[u];
flw[f].c -= minf;
flw[f ^ 1].c += minf;
}
ans += minf;
u = minu;
}
for ( f = aug[u]; f != -1; f = next[f] )
if ( flw[f].c && lev[u] == lev[ flw[f].v ] + 1 )
break;
if ( f != -1 ) {
v = flw[f].v;
aug[u] = f;
pre[v] = u;
u = v;
}
else {
if ( !( --sum_lev[ lev[u] ] ) )
break;
aug[u] = head[u];
for ( minlev = des, f = head[u]; f != -1; f = next[f] )
if ( flw[f].c )
minlev = MIN( minlev, lev[ flw[f].v ] );
lev[u] = minlev + 1;
sum_lev[ lev[u] ]++;
if ( u != src )
u = pre[u];
}
}
return ans;
}
int
main() {
int n, m;
int u, v, c;
while ( ~scanf("%d%d", &m, &n) ) {
memset( head + 1, -1, n * sizeof(int));
e = 0;
while ( m-- ) {
scanf("%d%d%d", &u, &v, &c);
addflw( u, v, c );
}
printf("%d\n", sap( 1, n ));
}
return 0;
}
单词解释:
drainage:n, 排水,排污,排水系统
ditch:n, 沟渠
augmentation:n, 增广
pond:n, 池塘
Bessie:人名,贝西
clover:n, 三叶草
patch:n, 小块土地,眼罩
regrow:vt, 再生,重新长出
drain:vt, 排水(排出),喝光,耗尽
nearby:adj, 附近的
stream:n, 溪流
ace:adj, 王牌的,一流的
regulator:n, 校准器,调整器
install:vt, 安装
rate:n, 比率,速率
gallon:n, 加仑,容量单位
layout:n, 布局
feed:vi, 流入
potentially:adv, 可能地,潜在地
complex:adj, 复杂的
network:n, 网络
determine:vt, 确定出
dig:vt, 挖掘
intersection:n, 交叉点,十字路口
designate:vt, 指定