天天看点

图论 最大流EK算法

今天接触了最大流,网上有很多ppt,耐心看下,再敲几遍代码大概就能懂意思了

EK 算法

关键是要理解要理解反悔的这个意思,因为每次当你选择了一种方式,但是这种方式不一定是最优的所以我们要再来建立一条反向边,

来完成反悔的策略

然后就是大概一直找增广路,改变最大的值,一直到找不到增广路为止

现在把模板的代码附上,并且给予注释

下面有两种方式一种是紫书上刘汝佳的代码,还有种是用链式前向星,还有种是dfs的方式,之后会继续补充

分别给出两种代码

struct Edge{

int from,to,cap,flow;

// from:起始边, to:终点边 , cap: 容量 , flow: 流量

Edge(int u,int v,int c,int f):from(u),to(v),cap(c),flow(f){}//内建函数

};

struct EdmondsKarp{

int n,m;

vector<Edge> edges;// edges 用来存所有的边, 正向反向边都要存

vector<int> G[maxn];//用来记录第i个点的每条边的序号

int a[maxn];//用来存增广路上的可改变的值

int p[maxn];// 用来记录入-编号

void init(int n){

for(int i=0;i<n;i++)G[i].clear();

edges.clear();

}

void Addedge(int from, int to ,int cap){ 

// 边的添加很有技巧

因为可以反悔,所以 我们每次要存入反向边,反向边的容量为0

当然有时候,根据题意的不同有时反向边的容量不为0,那么就按照题目的给的数据进行存储

edges.push_back(Edge(from, to, cap, 0));

edges.push_back(Edge(to, from, 0, 0));

m = edges.size();

// 这个也有技巧,其实这个和链式前向星很像(两条变的存储cnt++,但也是连在一起的,这个也是)

每次将这个点连接的边的序号push进去

G[from].push_back(m-2);

G[to].push_back(m-1);

}

int maxflow(int s,int t){

int flow = 0;

for(;;){

CLR(a);

queue<int> Q;

Q.push(s);

a[s]=inf;

while(!Q.empty()){

int x = Q.front();

Q.pop();

for(int i=0;i<G[x].size();i++){

//访问这个点中的所有边

Edge& e = edges[G[x][i]];

// e得到这个边的信息

if(!a[e.to] && e.cap>e.flow){

//这个部分比较难理解

!a[e.to]最小残留量为0是才会更新,如果已经不为0说明更新过不再更新

当前的容量必须大于流量

p[e.to]=G[x][i];

// 储存当前点的标号,用以后面更新

a[e.to]=min(a[x],e.cap-e.flow);

Q.push(e.to);

}

}

if(a[t])break;//如果被更新break

}

if(!a[t])break;

//此时找不到增广路

for(int u=t;u != s;u = edges[p[u]].from){

edges[p[u]].flow += a[t];

edges[p[u]^1].flow -= a[t];

}

flow += a[t];

}

return flow;

}

};

下面给出用链式前向星代码

void addedge(int u,int v,int w){//这边就是一个简单的链式前向星处理

pnt[cnt]=v;

nxt[cnt]=head[u];

cap[cnt]=w;

head[u]=cnt++;

}

void adddouble(int u,int v,int w){//同样是加边,两条

addedge(u,v,w);

addedge(v,u,0);

}

int maxflow(int s,int t){

int flow = 0;

for(;;){

CLR(a);

queue<int> q;

q.push(s);

a[s]=inf;//不要忘记初始化为inf

while(!q.empty()){

int u = q.front();

q.pop();

for(int i=head[u]; ~i; i= nxt[i]){

int v =pnt[i];

if(!a[v] && cap[i] > 0){

a[v]=min(cap[i],a[u]);

pre[v]=i;// 此时的pre也是记录当前点的标号,记住用链式前向星的时候这是i!

q.push(v);

}

}

if(a[t])break;

}

if(!a[t])break;

flow += a[t];

for(int i,u=t;u!=s;u = pnt[i^1]){// 这部分的更新也是。要对链式前向星理解才行呢!

i = pre[u];

cap[i] -= a[t];//此时我省略了流量,只用了流量所以加减就是反着的,理解理解

cap[i^i] += a[t];

}

}

return flow;

}