利用BFS找到所有的路径,只要找到一条路径没有满流量,就尽量往里面的边充流量,直到所有的路径都充满了不能再容纳新的流量为止。Edmonds-Karp算法是Ford-Fulkerson方法的一种实现,时间复杂度是O( n m 2 nm^2 nm2)。
HDU 1532 AC确认,这道题裸板子,但是起点终点固定为1和n,并且有可能有重边,没有改代码
#include
#include
#include
#include
/*
* 潘骏同志模拟的EK算法
*/
using namespace std;
long long n,m;
struct edge
{
long long to;
long long cap;
long long flow;
};
unordered_map egs[100005];
//存边,兼具邻接矩阵和邻接表的优点,找负边也很方便,存取都是O(1)
bool vis[100005];
long long cap[100005];
long long pre[100005];
long long E(long long s,long long t)//BFS过程
{
memset(vis,0,sizeof vis);
memset(cap,0,sizeof cap);
memset(pre,0,sizeof pre);
queue qq;
qq.push(s);
vis[s]=1;
cap[s]=0x3f3f3f3f3f3f3f3f;
while(!qq.empty())
{
long long now=qq.front();
qq.pop();
for(auto it:egs[now])
{
edge nxt=it.second;
if(!vis[nxt.to] && nxt.cap-nxt.flow>0)
{
cap[nxt.to]=min(cap[now],nxt.cap-nxt.flow);
pre[nxt.to]=now;
vis[nxt.to]=true;
if(cap[t])
{
return cap[t];
}
qq.push(nxt.to);
}
}
}
return 0;
}
long long K(long long s,long long t)//更新流量过程
{
long long ans=0;
long long greencap=E(s,t);//绿帽表示增量
while(greencap!=0)
{
ans+=greencap;
long long last=pre[t];
long long now=t;
while(last!=0)
{
egs[last][now].flow+=greencap;
egs[now][last].flow-=greencap;//用于反悔,万一还有别的路能走更大流量
now=last;
last=pre[now];
}
greencap=E(s,t);
}
return ans;
}
int main()
{
//freopen("testdata.in","r",stdin);
while(cin>>m>>n) {
for(int i=1;i<=n;i++) {
egs[i].clear();
}
for (long long i = 0; i < m; i++) {
long long a, b, c;
cin >> a >> b >> c;
edge temp;
temp.to = b;
temp.cap = c;
temp.flow = 0;
if(egs[a].find(b)==egs[a].end()){
egs[a][b] = temp;
} else {
egs[a][b].cap+=c;//如果有重边就加上容量把边合并了
}
temp.to = a;
temp.cap = 0;
if(egs[b].find(a)==egs[b].end()){
egs[b][a] = temp;
}
}
cout << K(1, n) << endl;
}
return 0;
}
Dinic算法是利用上述Edmonds-Karp算法的最短增广路思想改进来的,我们需要找到阻塞流(即不考虑反向弧的情况下的极大流)来增广。因为最多要找n-1次(刘汝佳说的,我不知道啊)阻塞流,每次处理O( n m nm nm),所以总的复杂度为O( n 2 m n^2 m n2m).实际上一些特殊的图里面还更快一点。
HDU 1532 AC确认,这道题裸板子,但是起点终点固定为1和n,并且有可能有重边,没有改代码
另外POJ 3469/百练3478 也是这套板子过的,就是时间有点丑陋8000+ms
#include
#include
#include
#include
/*
* 潘骏同志模拟的Dinic算法
*/
using namespace std;
long long n,m;
struct edge
{
long long to;
long long cap;
long long flow;
};
unordered_map egs[100005];
bool vis[100005];
long long dis[100005];
bool bfs(long long s,long long t)
{
memset(vis,0,sizeof vis);
memset(dis,0,sizeof dis);
queue qq;
qq.push(s);
vis[s]=1;
dis[s]=1;
while(!qq.empty())
{
long long now=qq.front();
qq.pop();
for(auto it:egs[now])
{
edge nxt=it.second;
if(!vis[nxt.to] && nxt.cap-nxt.flow>0)
{
dis[nxt.to]=dis[now]+1;//标记层次图
vis[nxt.to]=true;
qq.push(nxt.to);
}
}
}
if(dis[t]!=0)
{
return true;
} else
{
return false;
}
}
long long dfs(long long s,long long t,long long now,long long capper)
{
//利用DFS找所有的阻塞流,这一过程最多执行n-1次
if(now==t || capper==0)
{
//到达终点或者容量为零了就返回
return capper;
}
long long flows=0;
long long greenflow;
for(auto it : egs[now])
{
if(it.second.cap>it.second.flow && dis[now]+1==dis[it.second.to]) {
//判断是不是层次递增的允许弧,是否还有剩余流量
greenflow = dfs(s, t, it.second.to, min(capper, it.second.cap - it.second.flow));
if (greenflow > 0) {
//残量网络还有可以增广的路径,增广
egs[now][it.second.to].flow += greenflow;
egs[it.second.to][now].flow -= greenflow;
flows += greenflow;
capper -= greenflow;
if (capper == 0) {
return flows;
}
}
}
}
return flows;
}
int main()
{
//freopen("testdata.in","r",stdin);
while(cin>>m>>n) {
for(int i=1;i<=n;i++) {
egs[i].clear();
}
for (long long i = 0; i < m; i++) {
long long a, b, c;
cin >> a >> b >> c;
edge temp;
temp.to = b;
temp.cap = c;
temp.flow = 0;
if(egs[a].find(b)==egs[a].end()){
egs[a][b] = temp;
} else {
egs[a][b].cap+=c;
}
temp.to = a;
temp.cap = 0;
if(egs[b].find(a)==egs[b].end()){
egs[b][a] = temp;
}
}
long long ans=0;
while(bfs(1,n))
{
long long greencap=dfs(1,n,1,0x3f3f3f3f3f3f3f3f);
while(greencap)
{
ans+=greencap;
greencap=dfs(1,n,1,0x3f3f3f3f3f3f3f3f);
}
}
cout<
鸽了,爽。ISAP够用了,复杂度没啥区别的,就这样吧。。。
SAP即最短增广路算法,ISAP即改进的最短增广路算法。SAP其实思想和Dinic是一样的,但是Dinic找层次图是每次都要重新进行BFS更新,SAP是直接改标号。因为本质和Dinic是一样的,所以时间复杂度上界仍然需要O( n 2 m n^2m n2m).
HDU1532 AC确认,时间15ms,比dinic省时间多了,此处没有修改
#include
#include
#include
#include
/*
* 潘骏同志模拟的ISAP算法
*/
using namespace std;
int n,m;
struct edge {
int from;
int to;
int flow;
int cap;
int nxt;
edge(int f, int t, int c, int n) : from(f), to(t), cap(c), nxt(n) {
flow = 0;
}
};
vector edges;
int egs[505];
void addedge(int f,int t,int c) {
edges.emplace_back(f, t, c, egs[f]);
egs[f] = edges.size() - 1;
edges.emplace_back(t, f, 0, egs[t]);
egs[t] = edges.size() - 1;
}
int dis[505];
bool vis[505];
bool bfs(int s,int t) {
memset(vis, 0, sizeof vis);
memset(dis, 0, sizeof dis);
queue qq;
qq.push(t);
vis[t] = 1;
dis[t] = 0;
while (!qq.empty()) {
int now = qq.front();
qq.pop();
for (int i = egs[now]; i != -1; i = edges[i].nxt) {
if (!vis[edges[i].to] && edges[i].cap - edges[i].flow > 0) {
dis[edges[i].to] = dis[now] + 1;//标记层次图
vis[edges[i].to] = true;
qq.push(edges[i].to);
}
}
}
if (dis[t] != 0) {
return true;
} else {
return false;
}
}
int pre[505];
int aug(int s,int t) {
int now = t;
int greencap = 0x3f3f3f3f;
while (now != s) {
greencap = min(greencap, edges[pre[now]].cap - edges[pre[now]].flow);
now = edges[pre[now]].from;
}
now = t;
while (now != s) {
edges[pre[now]].flow += greencap;
edges[pre[now] ^ 1].flow -= greencap;
now = edges[pre[now]].from;
}
return greencap;
}
int num[505];
int cur[505];
int isap(int s,int t) {
bfs(s, t);
memset(num, 0, sizeof num);
for (int i = 1; i <= n; i++) {
cur[i] = egs[i];//当前待增广的弧初始化为头节点
num[dis[i]]++;//记录每一层有多少节点
}
int now = s;
int maxflow = 0;
while (dis[s] < n) {//如果源点到汇点的距离超过了n-1,说明有点经过了两次(从回边,因为总会有回边的flow<=cap),没有增广路存在了
if (now == t) {//到达汇点,把当前的增广路流量加入最大瘤并回到源点重新开始
maxflow += aug(s, t);
now = s;
}
bool isoperated = false;
for (int i = cur[now]; i != -1; i = edges[i].nxt) {
if (edges[i].flow < edges[i].cap && dis[edges[i].from] == dis[edges[i].to] + 1) {//是还有容量的容许弧
cur[now] = i;//前面的路径都增广过了,没必要增广了
pre[edges[i].to] = i;
now = edges[i].to;
isoperated = true;
break;//深度优先地进行增广
}
}
if (!isoperated) {//没有允许弧能支持增广了,撤退
int miner = n;
for (int i = egs[now]; i != -1; i = edges[i].nxt) {
if (edges[i].flow < edges[i].cap) {
miner = min(miner, dis[edges[i].to] + 1);//选取最接近汇点的点下次再来
}
}
if (--num[dis[now]] < 0) {
break;//改编号以后本层没有节点了,那么出现断层将没有增广路可以经过这里,那么就没有残量了,直接退出。
}
dis[now] = miner;
num[dis[now]]++;//修改编号下次再来
cur[now] = egs[now];//修改编号后所有的边都得重新检查,相当于dinic里重新找一条增广路
if (now != s)now = edges[pre[now]].from;//回溯,找下一个,源点则是无路可退
}
}
return maxflow;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
while(cin>>m>>n) {
memset(egs, -1, sizeof egs);
memset(pre,-1,sizeof pre);
edges.clear();
for (int i = 0; i < m; i++) {
int a, b, c;
cin >> a >> b >> c;
addedge(a, b, c);
}
cout<
s-t割定义(刘汝佳《算法竞赛入门经典》P369):
把所有顶点分成两个集合S和T=V-S,其中源点s在集合S中,汇点t在集合T中。如果把“起点在S中,重点在T中”的所有边删除,就无法从s到达t了。这样的集合划分称为一个s-t割,它的容量定义为: c ( S , T ) = ∑ u ∈ S , v ∈ T c ( u , v ) c(S,T)=\sum_{u\in S,v\in T}^{ } c(u,v) c(S,T)=∑u∈S,v∈Tc(u,v) ,即起点在S中,终点在T中的所有边的容量和。
最小割最大流定理(刘汝佳《算法竞赛入门经典》P370):
在增广路算法结束时,f是s-t最大流,(S,T)是s-t最小割。
其实就是在Dinic最后一次BFS的时候能到达的边放进S,不能到达的边放进T,我们已经求出的最大流就是这个割的容量。
此段代码插入最大流跑完以后的地方,如果只需要求容量其实根本不需要
vector S;
vector T;
for(int i=1;i<=n;i++)
{
if(dis[i]==0)
{
T.push_back(i);
}
else
{
S.push_back(i);
}
}
在Edmonds-Karp的基础上,我们只需要做一个贪心(这里是指找最短路作为增广路的方法很贪心,SPFA/Bellman-Ford 本身其实是DP来着),就可以了。每次找增广路的时候都挑一条最短路(费用最小路)去增广,而不是像原版Edmonds-Karp一样无脑BFS。我们同样要把所有的路都增广到无法再增加流量为止,这样算出来的流量依然是最大的。因为我们需要加入负边来让最大流有退路可走,所以不能直接用Dijkstra来走最短路,最常用还是SPFA。
洛谷P3381 AC确认(不开O2优化会T),该题是真的裸板子题,所以这里的版本根本没有改动
Update:改成链式前向星了,并且封装了mfmc函数,现在已经可以不开O2直接过了
#include
#include
#include
#include
#include
#include
using namespace std;
struct edge
{
int from;
int to;
int flow;
int cap;
int cost;
int nxt;
edge(int f,int t,int c,int co,int n):from(f),to(t),cap(c),cost(co),nxt(n){
flow=0;
}
};
int egs[50005];
vector edges;
void addedge(int f,int t,int c,int co){
edges.emplace_back(f,t,c,co,egs[f]);
egs[f]=edges.size()-1;
edges.emplace_back(t,f,0,-co,egs[t]);
egs[t]=edges.size()-1;
}
int dis[50005];
bool vis[50005];
int pre[50005];
int visitor[50005];
int flower[50005];
int total;
int bellmanford(int s,int t)
{
memset(dis,0x3f,sizeof dis);
memset(vis,0,sizeof vis);
memset(pre,-1,sizeof pre);//编号为0的边是存在的,所以初始化为-1
memset(visitor,0,sizeof visitor);
memset(flower,0,sizeof flower);
dis[s]=0;
flower[s]=0x3f3f3f3f;
deque qq;
qq.push_back(s);
vis[s]=true;
while(!qq.empty())
{
int now=qq.front();
qq.pop_front();
visitor[now]++;
if(visitor[now]>total)//因为一条路最多才n-1条边,如果超过n次遍历同一边肯定有负环
{
return -1;
}
for(int i=egs[now];i!=-1;i=edges[i].nxt)
{
int nxt=edges[i].to;
int tempflow=min(flower[now],edges[i].cap-edges[i].flow);
int lener=edges[i].cost;
if(edges[i].cap-edges[i].flow>0 && dis[nxt]>dis[now]+lener)
{
dis[nxt]=dis[now]+lener;
pre[nxt]=i;//松弛了的且不在队列的点才能进队列哦
flower[nxt]=tempflow;
if(!vis[nxt])
{
if((!qq.empty()) && dis[nxt] mfmc(int s,int t){
long long maxflow=0;
long long mincost=0;
long long nowflow=0;
while((nowflow=bellmanford(s,t))>0)//找不到增广路的时候才停止
{
//cout<>n>>m)
{
total=n;
long long s,t;
cin>>s>>t;
memset(egs,-1,sizeof egs);
edges.clear();
for(long long i=0; i>ss>>ee>>capper>>coster;
addedge(ss,ee,capper,coster);
}
pair mfmcer=mfmc(s,t);
cout<
参考了以下文章,在此表示感谢:
https://zhuanlan.zhihu.com/p/46039732
https://www.cnblogs.com/owenyu/p/6852664.html
https://www.renfei.org/blog/isap.html
维基百科Edmonds-Karp算法