文章目录
网络流
最大流
增广路思路
EK算法
/*
bfs + update
时间复杂度O(nm^2)
*/
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <cstdio>
#include <queue>
using namespace std;
typedef long long ll;
const int N = 1e6+10;
const ll inf =0x3f3f3f3f3f3f;
ll h[N],e[N],ne[N],val[N],idx;
int pre[N];
bool vis[N];
ll incf[N];//流过
int n,m,s,t;
int ans;
ll maxflow;
//正向和反向建边
void add(int a,int b,int c)
{
e[idx] = b,ne[idx] = h[a],val[idx] = c,h[a] = idx++;
e[idx] = a,ne[idx] = h[b],val[idx] = 0,h[b] = idx++;
}
//跑增广路
//如果存在增广路
bool bfs()
{
memset(vis,0,sizeof vis);
queue<int> q;
q.push(s);
vis[s] = 1;
incf[s] = inf;
while(q.size())
{
int u = q.front();
q.pop();
for(int i=h[u];~i;i=ne[i])
if(val[i])
{
int j = e[i];
if(vis[j])continue;
incf[j] = min(incf[u],val[i]);
pre[j] = i;
q.push(j);
vis[j] = 1;
if(j==t)return 1;
}
}
return 0;
}
//减容量 并且反向边加容量
void update()
{
int x = t;
while(x!=s)
{
int i = pre[x];
val[i] -= incf[t];
val[i^1] += incf[t];
x = e[i^1];
}
maxflow += incf[t];
}
int main()
{
memset(h,-1,sizeof h);
cin>>n>>m>>s>>t;
int a,b,c;
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&a,&b,&c);
add(a,b,c);
}
while(bfs())update();
cout<<maxflow<<endl;
return 0;
}
Dinic算法
/*
时间复杂度O(n^2m)
*/
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <cstdio>
#include <queue>
using namespace std;
typedef long long ll;
const int N = 1e6+10;
const ll inf =0x3f3f3f3f3f3f;
ll h[N],e[N],ne[N],val[N],idx;
int d[N];
ll incf[N];//流过
int n,m,s,t;
int ans;
ll maxflow;
//正向和反向建边
void add(int a,int b,int c)
{
e[idx] = b,ne[idx] = h[a],val[idx] = c,h[a] = idx++;
e[idx] = a,ne[idx] = h[b],val[idx] = 0,h[b] = idx++;
}
//跑增广路
//如果存在增广路
bool bfs()
{
memset(d,0,sizeof d);
queue<int> q;
q.push(s);d[s] = 1;
while(q.size())
{
int u = q.front();
q.pop();
for(int i=h[u];~i;i=ne[i])
if(val[i]&&!d[e[i]])
{
q.push(e[i]);
d[e[i]] = d[u] + 1;
if(e[i]==t)return 1;//可以到汇点 还可以跑增广路
}
}
return 0;//到不了汇点 不能跑增广路
}
//
//1.优化 并行增广
//2.去除 增广完毕的点
ll dinic(int u,ll flow)//flow 总流
{
if(u==t)return flow;
ll rest = flow,k;
for(int i=h[u];~i&&rest;i=ne[i])
if(val[i]&&d[e[i]]==d[u]+1)
{
int j = e[i];
k = dinic(j,min(rest,val[i]));
if(!k)d[j] = 0;//去除增广完毕的点
val[i] -= k;
val[i^1] += k;
rest -= k;
}
return flow - rest;
}
int main()
{
memset(h,-1,sizeof h);
cin>>n>>m>>s>>t;
int a,b,c;
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&a,&b,&c);
add(a,b,c);
}
ll flow = 0;
while(bfs())
while(flow=dinic(s,inf))maxflow += flow;
cout<<maxflow<<endl;
return 0;
}
ISAP算法
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <cstdio>
#include <queue>
using namespace std;
typedef long long ll;
const int N = 1e5+100;
const ll INF = 1e18+10;
ll cur[N],h[N],e[N],ne[N],val[N],idx;//邻接表
int n,m,s,t;
int d[N];//每一个点的层次 t=1
int gap[N];//gap数组优化 当某一层为空的时候那就是出现了断层必不存在增广路
ll max_flow;
inline void add(int a,int b,ll c)
{
e[idx] = b,ne[idx] = h[a],val[idx] = c,h[a] = idx++;
e[idx] = a,ne[idx] = h[b],val[idx] = 0,h[b] = idx++;
}
//反向bfs求层次图
void inver_bfs()
{
queue<int> q;
q.push(t);
++gap[d[t]=1];
while(q.size())
{
int u = q.front();
q.pop();
for(int i=h[u];~i;i=ne[i])
{
int j = e[i];
if(d[j])continue;
q.push(j);
gap[d[j]=d[u]+1]++;
}
}
}
ll dfs(int u,ll flow)
{
if(flow==0)return 0;
if(u==t)
{
max_flow += flow;
return flow;
}
ll used = 0;
for(ll &i=cur[u];~i;i=ne[i])
{
int j = e[i];
if(d[j]==d[u]-1)
{
ll k = dfs(j,min(flow-used,val[i]));
if(k)val[i]-=k,val[i^1]+=k,used+=k;
if(used==flow)return flow;
}
}
//出现断层则break :令d[s]=n+1;
//改点在这一层的边都遍历完了,直接令其高度加1
(--gap[d[u]])?(++gap[++d[u]]):d[s] = n+1;
return used;
}
ll isap()
{
//先反向建图一次
inver_bfs();
while(d[s]<=n)
{
memcpy(cur,h,sizeof(h));
dfs(s,INF);
}
return max_flow;
}
int main()
{
cin>>n>>m>>s>>t;
int a,b;
ll c;
memset(h,-1,sizeof h);
for(int i=1;i<=m;i++)
{
scanf("%d%d%lld",&a,&b,&c);
add(a,b,c);
}
cout<<isap()<<endl;
return 0;
}
最高标志预流推进算法
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <cstdio>
#include <queue>
using namespace std;
typedef long long ll;
const int N = 2e4+10;
const int M = 4e5+10;
const int INF = 0x3f3f3f3f;
ll edge[M];
int h[N],e[M],idx,ne[M];
int n,m,s,t;
ll reser[N];//存储每一个点还剩余多少的流量
int gap[N];
int hi[N];
bool vis[N];
priority_queue<int,vector<int>,greater<int>> hq;
inline void add(int a,int b,ll c)
{
e[idx] = b,ne[idx] = h[a],edge[idx] = c,h[a] = idx++;
e[idx] = a,ne[idx] = h[b],edge[idx] = 0,h[b] = idx++;
}
//给所有的点贴上高度标签
inline bool bfs()
{
memset(hi,0x3f,sizeof(int)*n);
hi[t] = 0;
queue<int> q;
q.push(t);
while(q.size())
{
int u = q.front();
q.pop();
for(int i=h[u];~i;i=ne[i])
{
int j = e[i];
if(edge[i^1]&&hi[j]>hi[u]+1)
{
hi[j] = hi[u] + 1;
q.push(j);
}
}
}
return hi[s]!=INF;
}
inline void push(int u)
{
for(int i=h[u];~i;i=ne[i])
{
int j = e[i];
if(edge[i]&&(hi[j]+1==hi[u]))
{
ll k = min(reser[u],edge[i]);
edge[i] -= k;
edge[i^1] += k;
reser[u] -= k;
reser[j] += k;
if((j!=s)&&(j!=t)&&(!vis[j]))
{
hq.push(j);
vis[j] = 1;
}
if(!reser[u])break;
}
}
}
//重贴标签使其可以流向最低的点
inline void relabel(int u)
{
hi[u] = INF;
for(int i=h[u];~i;i=ne[i])
{
int j = e[i];
if(edge[i]&&(hi[j]+1<hi[u]))hi[u] = hi[j]+1;
}
}
inline ll hlpp()
{
if(!bfs())return 0;
hi[s] = n;//给源点贴上最高标签
memset(gap,0,sizeof(gap));
for(int i=1;i<=n;i++)
if(hi[i]!=INF)gap[hi[i]]++;
for(int i=h[s];~i;i=ne[i])
{
int j = e[i];
if(int f = edge[i])
{
edge[i] -= f;
edge[i^1] += f;
reser[s] -= f;
reser[j] += f;
if(j!=s&&j!=t&&!vis[j])
{
hq.push(j);
vis[j] = 1;
}
}
}
while(hq.size())
{
int t = hq.top();
hq.pop();
vis[t] = 0;
push(t);
if(reser[t])
{
//如果出现断层的化 那么h[i]>h[v]的点都不能向下传递到汇点
//把他们的高度置为 n+1 全部送回源点
gap[hi[t]]--;
if(!gap[hi[t]])
{
for(int v=1;v<=n;v++)
{
if(v!=t&&v!=s&&(hi[v]>hi[t])&&(hi[v]<n+1))
{
hi[v] = n+1;
}
}
}
//重贴标签然后传递流
relabel(t);gap[hi[t]]++;
hq.push(t);vis[t] = 1;
}
}
return reser[t];
}
int main()
{
scanf("%d%d%d%d",&n,&m,&s,&t);
memset(h,-1,sizeof h);
int a,b;
ll c;
for(int i=1;i<=m;i++)
{
scanf("%d%d%lld",&a,&b,&c);
add(a,b,c);
}
cout<<hlpp()<<endl;
return 0;
}
费用流
模板题:K取方格数
//建图 方式
//1.裂点 成 入点 和 出点
//2.在入点 和 出点上建立 1条带权容量为1的边 和 一条权为0容量为k-1的边
//源点是1 汇点是 n + n
//建边的方式:
//
//然后跑一遍最大费用最大流算法即可
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <string>
#include <queue>
using namespace std;
typedef long long ll;
const int N = 1e6+10;
ll h[N],e[N],val[N],ne[N],edge[N],idx;
int pre[N];
bool st[N];
int d[N];
ll incf[N];//每 点 的剩余容量
int s,t;
ll maxflow;
ll ans;
int n,k;
int num(int i,int j,int k)
{
return (i-1)*n + j + k*n*n;
}
void add(int a,int b,int z,int c)//z表示容量 c表示边权
{
e[idx] = b,edge[idx] = z,val[idx] = c,ne[idx] = h[a],h[a]=idx++;
e[idx] = a,edge[idx] = 0,val[idx] = -c,ne[idx] = h[b],h[b]=idx++;
}
//找到最短路
bool spfa()
{
queue<int> q;
memset(d,0xcf,sizeof d);
memset(st,0,sizeof st);
q.push(s);
d[s] = 0;
st[s] = 1;
incf[s] = 1 << 30;
while(q.size())
{
int u = q.front();
q.pop();
st[u] = 0;
for(int i=h[u];~i;i=ne[i])
if(edge[i])
{
int j = e[i];
if(d[j]<d[u]+val[i])
{
d[j] = d[u] + val[i];
incf[j] = min(incf[u],edge[i]);
pre[j] = i;
if(!st[j])st[j] = 1,q.push(j);
}
}
}
if(d[t]==0xcfcfcfcf)return false;
return true;
}
//更新残余网络
int update()
{
int x = t;
while(x!=s)
{
int i = pre[x];
edge[i] -= incf[t];
edge[i^1] += incf[t];
x = e[i^1];
}
maxflow += incf[t];
ans += d[t]*incf[t];
}
int main()
{
memset(h,-1,sizeof h);
cin>>n>>k;
s = 1, t = 2*n*n;
int a;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
cin>>a;
add(num(i,j,0),num(i,j,1),1,a);
add(num(i,j,0),num(i,j,1),k-1,0);
if(i<n)add(num(i,j,1),num(i+1,j,0),k,0);
if(j<n)add(num(i,j,1),num(i,j+1,0),k,0);
}
while(spfa())update();
cout<<ans<<endl;
return 0;
}
最小割
最大流最小割定理:
最大流==最小割
求割边的数量:令每条边的容量为1即可 跑最大流算法即可
经典问题:二选其一
有n个物品 物品i放进集合A有代价a,放进有代价b,如果物品i,j不放在一个集合有代价c
那么建图 S源点代表集合A T汇点表示集合B
每一个物品 1.S向其连一条有向边容量为a
2.物品向T连一条有向边容量为b
3.物品u,v之间连一条 容量为w的双向边
上下界网络流
无源汇上下界可行流
无源汇网络是一个循环流
必要流:所有边的下界流量之和
对于每一个点 流经它的必要流不一定满足流量守恒,定义 A [ i ] = f 入 − f 出 A[i] = f入 - f出 A[i]=f入−f出
如果$A[i]小于0 那么我们就建立一条 i 点到虚拟汇点T’的一条流量为A[i]的边 如果流量大于0反之 $
其他边的容量为 f 上 界 − f 下 界 f上界 - f下界 f上界−f下界 这种边称之为附加边 建立的过程中给每一条附加边一个编号 方便后面统计附加流的大小 所构成的流成为附加流
在这样的网络上从虚拟源点S’ 到虚拟汇点T’ 跑一次最大流
如果最大流 等于 从虚拟源点加的边之和 那么可行流存在(我们加的边对于S’和T‘来说是流量守恒的,显然)
A n s = 附 加 流 + 必 要 流 Ans = 附加流 + 必要流 Ans=附加流+必要流
模板题:
https://loj.ac/problem/115
/*
算法实现:见博客解析
*/
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <cstdio>
#include <queue>
using namespace std;
typedef long long ll;
const int N = 1e6+10;
const ll inf =0x3f3f3f3f3f3f;
ll h[N],e[N],ne[N],val[N],idx;
int d[N];
ll incf[N];//流过
int n,m,s,t;
int ans;
ll maxflow;
//正向和反向建边
void add(int a,int b,int c)
{
e[idx] = b,ne[idx] = h[a],val[idx] = c,h[a] = idx++;
e[idx] = a,ne[idx] = h[b],val[idx] = 0,h[b] = idx++;
}
//跑增广路
//如果存在增广路
bool bfs()
{
memset(d,0,sizeof d);
queue<int> q;
q.push(s);d[s] = 1;
while(q.size())
{
int u = q.front();
q.pop();
for(int i=h[u];~i;i=ne[i])
if(val[i]&&!d[e[i]])
{
q.push(e[i]);
d[e[i]] = d[u] + 1;
if(e[i]==t)return 1;//可以到汇点 还可以跑增广路
}
}
return 0;//到不了汇点 不能跑增广路
}
//1.优化 并行增广
//2.去除 增广完毕的点
ll dinic(int u,ll flow)//flow 总流
{
if(u==t)return flow;
ll rest = flow,k;
for(int i=h[u];~i&&rest;i=ne[i])
if(val[i]&&d[e[i]]==d[u]+1)
{
int j = e[i];
k = dinic(j,min(rest,val[i]));
if(!k)d[j] = 0;//去除增广完毕的点
val[i] -= k;
val[i^1] += k;
rest -= k;
}
return flow - rest;
}
int main()
{
memset(h,-1,sizeof h);
cin>>n>>m>>s>>t;
int a,b,c;
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&a,&b,&c);
add(a,b,c);
}
ll flow = 0;
while(bfs())
while(flow=dinic(s,inf))maxflow += flow;
cout<<maxflow<<endl;
return 0;
}
有源汇上下界可行流
思路:在S和T之间加一条上界为00 下界为 0 的边即可
//照抄无源汇的代码就🆗了
//就是要加条边就行
有源汇上下界最大流
思路:
A n s = f l o w 1 ( 可 行 解 ) + f l o w 2 ( 在 原 汇 点 和 原 源 点 之 间 的 最 大 流 ) Ans = flow1(可行解) + flow2(在原汇点和原源点之间的最大流) Ans=flow1(可行解)+flow2(在原汇点和原源点之间的最大流)
证明过于玄学(其实就是我不会)
注意:算法实现的过程中注意把虚拟源点和虚拟汇点之间的超级边(上界是inf,下界是0)删去。
模板题:https://loj.ac/problem/116
/*
*/
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <cstdio>
#include <queue>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
const int INF = 0x3f3f3f3f;
int h[N], e[N], ne[N], idx;
ll low[N];
ll val[N];
int id[N]; //记录每一条边的残余网络中的id号码
int lo[N];
int A[N];
int n, m, s, t;
int d[N];
int ss, tt; //虚拟源点和虚拟汇点
void add(int a, int b, ll c) {
e[idx] = b, ne[idx] = h[a], val[idx] = c, h[a] = idx++;
e[idx] = a, ne[idx] = h[b], val[idx] = 0, h[b] = idx++;
}
//跑增广路
//如果存在增广路
bool bfs(int ss, int tt) {
memset(d, 0, sizeof d);
queue<int> q;
q.push(ss);
d[ss] = 1;
while (q.size()) {
int u = q.front();
q.pop();
for (int i = h[u]; ~i; i = ne[i])
if (val[i] && !d[e[i]]) {
q.push(e[i]);
d[e[i]] = d[u] + 1;
if (e[i] == tt)
return 1; //可以到汇点 还可以跑增广路
}
}
return 0; //到不了汇点 不能跑增广路
}
//
// 1.优化 并行增广
// 2.去除 增广完毕的点
ll dinic(int u, int tt, ll flow) // flow 总流
{
if (u == tt)
return flow;
ll rest = flow, k;
for (int i = h[u]; ~i && rest; i = ne[i])
if (val[i] && d[e[i]] == d[u] + 1) {
int j = e[i];
k = dinic(j, tt, min(rest, val[i]));
if (!k)
d[j] = 0; //去除增广完毕的点
val[i] -= k;
val[i ^ 1] += k;
rest -= k;
}
return flow - rest;
}
int main() {
memset(h, -1, sizeof h);
cin >> n >> m >> s >> t;
int a, b, up;
for (int i = 1; i <= m; i++) {
scanf("%d%d%d%d", &a, &b, &lo[i], &up);
A[a] -= lo[i];
A[b] += lo[i];
id[i] = idx;
add(a, b, up - lo[i]);
}
//建立平衡边
//设置虚拟源点
ll sum_ss = 0;
ss = n + 1;
tt = n + 2;
add(t, s, INF);
for (int i = 1; i <= n; i++) {
if (A[i] < 0)
add(i, tt, -A[i]);
if (A[i] > 0) {
sum_ss += A[i];
add(ss, i, A[i]);
}
}
add(t, s, INF);
ll flow1 = 0;
ll flow2 = 0;
ll flow = 0;
while (bfs(ss, tt))
while (flow = dinic(ss, tt, INF)) flow1 += flow;
if (flow1 == sum_ss) //存在可行流flow1
{
//最大流就是再跑一次
while (bfs(s, t))
while (flow = dinic(s, t, INF)) flow2 += flow;
cout << flow2 << endl;
} else
cout << "please go home to sleep" << endl;
return 0;
}
有源汇上下界最小流
删除所有的附加边后退回流量即可!
好像有点麻烦算了不码了
网络流24题
待我写!!