一个小知识:重启控制台输入
在调试过程中,需要system("pause")一下,如果freopen了in.txt会发现无法pause。这需要重新启用控制台输入。
- windows下:freopen("CON", "r", stdin)
- linux下:freopen("/dev/console", "r", stdin)
Ford-Fulkerson算法:朴素思路
一开始,每条路径可用流量值都是题目输入的流量。
每次随机从源点走到终点,在这条路径上用瓶颈流量来更新这条路上所有边的可用流量值。
如此迭代,直到从源点无法到达终点。
Ford-Fulkerson算法是一种原理,它的基础是“最大流最小割”定理。
最大流最小割定理其实就是木桶原理、链条原理。链条中最薄弱的一环决定了链条的强度。
最大流最小割定理:一个s-t流的最大值,等于其s-t割的最小容量。
一个定理
对于一个网络流,如果每条边的最大流量值都是整数,那么整个网络的最大流量值为整数,并且每条边的实际流量也是整数。
这可以根据Ford-Fulkerson算法的求解过程推理而得,因为每次更新的都是一个整数值。
Edmond-Karp算法(最短增广路算法)
使用广搜算法,每次选取源点到终点的最短路径作为增广路。
复杂度分析:需要进行N(边数)次迭代,每次迭代都需要找到一条增广路,找一条增广路需要进行一次广搜,一次广搜的最差复杂度为O(N×M),即边数乘以点数。总的复杂度为O(N×M×N),即:边数×边数×点数
如下图,当终结点所在深度固定时,每次广搜都必须遍历全部结点,这是有最坏时间复杂度。并且,每次只能删除掉一条最小边(故需删除M(边数)次)
EK算法的最佳情况
Dicnic算法(连续最短增广路算法)
深搜广搜相结合
Dicnic算法就是为了解决EK算法的最差情况而生的,然而对于EK算法的最佳情况,Dicnic显得多此一举。
Dinic算法的思想也是分阶段地在层次网络中增广。
它与最短增广路算法不同之处是:最短增广路每个阶段执行完一次BFS增广后,要重新启动BFS从源点Vs开始寻找另一条增广路;而在Dinic算法中,只需一次DFS过程就可以实现多次增广,这是Dinic算法的巧妙之处。
进行完一次BFS之后,终结点的深度就确定了,假设为k。使用DFS可以找到深度为k的全部增广路。每进行一次BFS,都会使得终结点深度变得更深。
Dicnic算法步骤
- 初始化容量网络和网络流
- 构造残留网络和层次网络,若汇点不在层次网络中则算法结束 输出最大流
- 在层次网络中用一次DFS进行增广,DFS执行完毕,该阶段的增广也就完毕了。
- 转至步骤2
其它算法
SAP算法、ISAP算法
一道裸题:hdu1532
本题是最大流问题最原始的形式 给定M个结点,N条有向边
此题需要注意
- 多个样例,注意初始化数据,每次接受新输入之前清空旧数据
- 有重边,这要求添加边时不能直接赋值,而要用+=
EK算法
#include<iostream>
#include<stdio.h>
#include<list>
#include<string.h>
#include<queue>
#include<algorithm>
using namespace std;
int N, M;
const int maxn = 207;
list<int>g[maxn];
int flow[maxn][maxn];
int pre[maxn];
void bfs(){
memset(pre, -1, sizeof(pre));
queue<int>q;
q.push(1);
pre[1] = 0;
while (q.empty() == false){
int now = q.front();
q.pop();
for (list<int>::iterator i = g[now].begin(); i != g[now].end(); i++){
int to = *i;
if (pre[to] == -1 && flow[now][to] > 0){
q.push(to);
pre[to] = now;
if(to==M)return;//如果找到了终结点,直接返回
}
}
}
}
int getMin(){
int ans = 1e9;
for (int i = M; i != 1; i = pre[i]){
ans = min(ans, flow[pre[i]][i]);
}
return ans;
}
void update(int mi){
for (int i = M; i != 1; i = pre[i]){
flow[pre[i]][i] -= mi;
}
}
int main(){
freopen("in.txt", "r", stdin);
while (cin >> N >> M){
for (int i = 1; i <= M; i++)g[i].clear();
memset(flow, 0, sizeof(flow));
for (int i = 0; i < N; i++){
int from, to, f;
cin >> from >> to >> f;
flow[from][to] += f;
g[from].push_back(to);
}
int ans = 0;
while (true){
bfs();
if (pre[M] == -1)break;
int mi = getMin();
update(mi);
ans += mi;
}
cout << ans << endl;
}
}
网上有此题代码,这份代码直接进行深搜,相当于dicnic算法没有BFS阶段,效率不知如何
#include <cstdio>
#include <cstring>
#include <iostream>
#include <string>
#include <algorithm>
#include <map>
#include <vector>
using namespace std;
const int N = 1100;
const int INF = 0x3f3f3f3f;
struct Node
{
int to;//终点
int cap; //容量
int rev; //反向边
};
vector<Node> v[N];
bool used[N];
void add_Node(int from,int to,int cap) //重边情况不影响
{
v[from].push_back((Node){to,cap,v[to].size()});
v[to].push_back((Node){from,0,v[from].size()-1});
}
int dfs(int s,int t,int f)
{
if(s==t)
return f;
used[s]=true;
for(int i=0;i<v[s].size();i++)
{
Node &tmp = v[s][i]; //注意
if(used[tmp.to]==false && tmp.cap>0)
{
int d=dfs(tmp.to,t,min(f,tmp.cap));
if(d>0)
{
tmp.cap-=d;
v[tmp.to][tmp.rev].cap+=d;
return d;
}
}
}
return 0;
}
int max_flow(int s,int t)
{
int flow=0;
for(;;){
memset(used,false,sizeof(used));
int f=dfs(s,t,INF);
if(f==0)
return flow;
flow+=f;
}
}
int main()
{
int n,m;
while(~scanf("%d%d",&n,&m))
{
memset(v,0,sizeof(v));
for(int i=0;i<n;i++)
{
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
add_Node(x,y,z);
}
printf("%d
",max_flow(1,m));
}
}
Dicnic算法
#include <stdio.h>
#include <string.h>
#define VM 2000
#define EM 205500
#define inf 0x3f3f3f3f
struct Edge
{
int frm,to,cap,next;
}edge[EM];
int head[VM],dep[VM],ep; //dep为点的层次
void addedge (int cu,int cv,int cw) //第一条边下标必须为偶数
{
edge[ep].frm = cu;
edge[ep].to = cv;
edge[ep].cap = cw;
edge[ep].next = head[cu];
head[cu] = ep;
ep ++;
edge[ep].frm = cv;
edge[ep].to = cu;
edge[ep].cap = 0;
edge[ep].next = head[cv];
head[cv] = ep;
ep ++;
}
int BFS (int src,int des) //求出层次图
{
int que[VM],i,front = 0,rear = 0;
memset (dep,-1,sizeof(dep));
que[rear++] = src;
dep[src] = 0;
while (front != rear)
{
int u = que[front++];
front = front%VM;
for (i = head[u];i != -1;i = edge[i].next)
{
int v = edge[i].to;
if (edge[i].cap > 0&&dep[v] == -1) //容量大于0&&未在dep中
{
dep[v] = dep[u] + 1; //建立层次图
que[rear ++] = v;
rear = rear % VM;
if (v == des) //找到汇点 返回
return 1;
}
}
}
return 0;
}
int dinic (int src,int des)
{
int i,res = 0,top;
int stack[VM]; //stack为栈,存储当前增广路
int cur[VM]; //存储当前点的后继 跟head是一样的
while (BFS(src,des)) //if BFS找到增广路
{
memcpy (cur,head,sizeof (head));
int u = src; //u为当前结点
top = 0;
while (1)
{
if (u == des) //增广路已全部进栈
{
int min = inf,loc ;
for (i = 0;i < top;i ++) //找最小的增广跟并loc记录其在stack中位置
if (min > edge[stack[i]].cap) //以便退回该边继续DFS
{
min = edge[stack[i]].cap;
loc = i;
}
for (i = 0;i < top;i ++) //偶数^1 相当加1 奇数^1相当减1 当正向边 = 0&&路径不合适时,正加负减
{ //偶数是正向边,奇数是负向边,边从0开始
edge[stack[i]].cap -= min;
edge[stack[i]^1].cap += min;
} //将增广路中的所有边修改
res += min;
top = loc;
u = edge[stack[top]].frm; //当前结点修改为最小边的起点
}
for (i = cur[u];i != -1;cur[u] = i = edge[i].next) //找到当前结点对应的下一条边
if (edge[i].cap != 0&&dep[u] + 1 == dep[edge[i].to])//不满足条件时,修改cur值(去掉不合适的占)eg:1-->2 1-->3 1-->4 有边 但只有
break; // 1-->4 这条边满足条件 就把1到2、3的边给去掉
if (cur[u] != -1) //当前结点的下一条边存在
{
stack[top ++] = cur[u]; //把该边放入栈中
u = edge[cur[u]].to; //再从下个点开始找
}
else
{
if (top == 0) //当前结点无未遍历的下一条边且栈空,DFS找不到下一条增广路
break;
dep[u] = -1; //当前结点不在增广路中,剔除该点
u = edge[stack[--top]].frm; //退栈 回朔,继续查找
}
}
}
return res;
}
int main ()///坐标从0或1开始均可 注意别忘记下面的2个初始化
{
int np,nc,m,v1,v2,w,n;
int src,des;
char str[20];
while (scanf ("%d%d%d%d",&n,&np,&nc,&m)!=EOF)
{
ep = 0;//边的初始化
src = n;
des = n+1;
memset (head,-1,sizeof(head));///这里初始化
while (m --)
{
scanf ("%s",str);
sscanf (str,"(%d,%d)%d",&v1,&v2,&w);
addedge (v1,v2,w);
}
while (np --)
{
scanf ("%s",str);
sscanf (str,"(%d)%d",&v2,&w);
addedge (src,v2,w);
}
while (nc--)
{
scanf ("%s",str);
sscanf (str,"(%d)%d",&v1,&w);
addedge (v1,des,w);
}
int ans = dinic (src,des);
printf ("%d
",ans);
}
return 0;
}
SAP算法
#include <stdio.h>
#include <string.h>
const int VM = 110, EM = 20500, inf = 0x3f3f3f3f;
struct Edge
{
int to, frm, nxt, cap;
}edge[EM];
int head[VM], ep, n, src, des;
int dep[VM], gap[VM]; //gap[x]=y:说明残留网络中dep[i]=x的个数为y
void addedge(int u, int v, int c)
{
edge[ep].frm = u;
edge[ep].to = v;
edge[ep].cap = c;
edge[ep].nxt = head[u];
head[u] = ep++;
edge[ep].frm = v;
edge[ep].to = u;
edge[ep].cap = 0;
edge[ep].nxt = head[v];
head[v] = ep++;
}
void BFS()
{
memset(dep, -1, sizeof(dep));
memset(gap, 0, sizeof(gap));
gap[0] = 1; //说明此时有1个dep[i] = 0
int que[VM], front = 0, rear = 0;
dep[des] = 0;
que[rear++] = des;
int u, v;
while (front != rear)
{
u = que[front++];
front = front%VM;
for (int i=head[u]; i!=-1; i=edge[i].nxt)
{
v = edge[i].to;
if (edge[i].cap != 0 || dep[v] != -1)
continue;
que[rear++] = v;
rear = rear % VM;
++gap[dep[v] = dep[u] + 1]; //求出各层次的数量
}
}
}
int Sap()
{
int res = 0;
BFS();
int cur[VM];
int stack[VM], top = 0;
memcpy(cur, head, sizeof(head));
int u = src, i;
while (dep[src] < n)
{
if (u == des)
{
int temp = inf, inser = n;
for (i=0; i!=top; ++i)
if (temp > edge[stack[i]].cap)
{
temp = edge[stack[i]].cap;
inser = i;
}
for (i=0; i!=top; ++i)
{
edge[stack[i]].cap -= temp;
edge[stack[i]^1].cap += temp;
}
res += temp;
top = inser;
u = edge[stack[top]].frm;
}
if (dep[u] != 0 && gap[dep[u] -1] == 0)//出现断层,无增广路
break;
for (i = cur[u]; i != -1; i = edge[i].nxt)//遍历与u相连的未遍历结点
if (dep[edge[i].to] != -1)
if (edge[i].cap != 0 && dep[u] == dep[edge[i].to] + 1) //层序关系, 找到允许
break;
if (i != -1)//找到允许弧
{
cur[u] = i;
stack[top++] = i;//加入路径栈
u = edge[i].to;//查找下一个结点
}
else //无允许的路径,修改标号 当前点的标号比与之相连的点中最小的多1
{
int min = n;
for (i = head[u]; i != -1; i = edge[i].nxt) //找到与u相连的v中dep[v]最小的点
{
if (edge[i].cap == 0)
continue;
if (min > dep[edge[i].to])
{
min = dep[edge[i].to];
cur[u] = i; //最小标号就是最新的允许弧
}
}
--gap[dep[u]]; //dep[u] 的个数变化了 所以修改gap
++gap[dep[u] = min + 1]; //将dep[u]设为min(dep[v]) + 1, 同时修改相应的gap[]
if (u != src) //该点非源点&&以u开始的允许弧不存在,退点
u = edge[stack[--top]].frm;
}
}
return res;
}
int main()///坐标从0开始
{
int i, np, nc, m;
char str[10];
while (scanf("%d %d %d %d", &n, &np, &nc, &m) != EOF)
{
ep = 0;
memset(head, -1, sizeof(head));
int u, v, c;
src = n, des = n + 1;
n += 2;
for (i=0; i!=m; ++i)
{
scanf("%stack", str);
sscanf(str, "(%d,%d)%d", &u, &v, &c);
addedge(u, v, c);
}
for (i=0; i!=np; ++i)
{
scanf("%stack", str);
sscanf(str, "(%d)%d", &v, &c);
addedge(src, v, c);
}
for (i=0; i!=nc; ++i)
{
scanf("%stack", str);
sscanf(str, "(%d)%d", &u, &c);
addedge(u, des, c);
}
printf("%d
", Sap());
}
return 0;
}