网络流模板与经典模型
网络流模板与经典模型
1.模板
dinic模板(常规的最大流模板,算法效率能满足大部分题)
#include<iostream>
#include<queue>
#include<cstring>
#include<algorithm>
using namespace std;
#define ll long long
const ll N=10007;
const ll M=200007;
const ll inf=1e18;
ll n,m,S,T;
ll h[N],e[M],f[M],ne[M],tot;
ll d[N],cur[N];
queue<ll>sa;
void add(ll u,ll v,ll z){
e[tot]=v;
f[tot]=z;
ne[tot]=h[u];
h[u]=tot++;
e[tot]=u;
f[tot]=0;
ne[tot]=h[v];
h[v]=tot++;
}
bool bfs(){
memset(d,-1,sizeof(d));
while(!sa.empty())sa.pop();
sa.push(S);
d[S]=0;
cur[S]=h[S];
while(!sa.empty()){
ll p=sa.front();
sa.pop();
for(int i=h[p];~i;i=ne[i]){
ll to=e[i];
if(d[to]==-1&&f[i]){
d[to]=d[p]+1;
cur[to]=h[to];
if(to==T){
return true;
}
sa.push(to);
}
}
}
return false;
}
ll dfs(ll p,ll now){
if(p==T)return now;
ll sum=0;
for(int i=cur[p];~i&&sum<now;i=ne[i]){
cur[p]=i;
ll to=e[i];
if(d[to]==d[p]+1&&f[i]){
ll lin=dfs(to,min(f[i],now-sum));
if(lin==0){
d[to]=-1;
}
else{
f[i]-=lin;
f[i^1]+=lin;
sum+=lin;
}
}
}
return sum;
}
ll dinic(){
ll sum=0;
while(bfs()){
sum+=dfs(S,inf);
}
return sum;
}
int main(){
scanf("%lld%lld%lld%lld", &n, &m, &S, &T);
memset(h, -1, sizeof h);
while(m--){
ll u, v, z;
scanf("%lld%lld%lld",&u,&v,&z);
add(u,v,z);
}
printf("%lld\n", dinic());
return 0;
}
ISAP模板(比dinic更快,写题超时不妨试试这种)
#include<iostream>
#include<cstring>
using namespace std;
const int maxn=100050;
const int maxm=200050;
const int inf=1e9;
struct edge {
int to,w,next;
}e[maxm<<2];
int n,m;
int head[maxn],cnt=1,gap[maxn],last[maxn],d[maxn],que[maxn],ql,qr;
void init(){
cnt=1;
memset(head,0,sizeof(head));
}
void add(int u,int to,int w) {
e[++cnt].to=to;
e[cnt].w=w;
e[cnt].next=head[u];
head[u]=cnt;
e[++cnt].to=u;
e[cnt].w=0;
e[cnt].next=head[to];
head[to]=cnt;
}
int aug(int x,int s,int t,int mi) {
if(x==t)return mi;
int flow=0;
for(int &i=last[x],v=e[i].to;i;i=e[i].next,v=e[i].to){
if(d[x]==d[v]+1){
int tmp=aug(v,s,t,min(mi,e[i].w));
flow+=tmp;
mi-=tmp;
e[i].w-=tmp;
e[i^1].w+=tmp;
if(!mi)return flow;
}
}
if(!(--gap[d[x]]))d[s]=t+1;
++gap[++d[x]],last[x]=head[x];
return flow;
}
int dinic(int s,int t){
fill(gap,gap+t+1,0);
fill(d,d+t+1,0);
++gap[d[t]=1];
for(int i=1;i<=t;++i)last[i]=head[i];
que[ql=qr=1]=t;
while (ql<=qr) {
int x=que[ql++];
for(int i=head[x],v=e[i].to;i;i=e[i].next,v=e[i].to){
if(!d[v]){
++gap[d[v]=d[x]+1];
que[++qr]=v;
}
}
}
int ans=aug(s,s,t,inf);
while(d[s]<=t)ans+=aug(s,s,t,inf);
return ans;
}
最小费最大流
#
include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N=5007;
const ll inf=1e18;
#define PII pair <ll,ll>
#define fr first
#define sc second
struct madoka{
vector<PII>ld;
struct edge{
ll v,f,c,r;
};
ll V=0,h[N],dis[N],pv[N],pe[N];
vector<edge>G[N];
inline void init(ll n){
for(ll i=0;i<=V;i++){
G[i].clear();
}
V=n;
ld.clear();
}
inline void add(ll u,ll v,ll f,ll c){
G[u].push_back({v,f,c,(ll)G[v].size()});
G[v].push_back({u,0,-c,(ll)G[u].size()-1});
}
PII dinic(ll s,ll t,ll Flow){
ll cost=0,flow=0,newflow;
fill(h,h+1+V,0);
while(Flow){
priority_queue<PII,vector<PII>,greater<PII> >Q;
fill(dis,dis+V+1,inf);
dis[s]=0,Q.push({0,s});
while(!Q.empty()){
PII now=Q.top();Q.pop();
ll u=now.sc;
if(dis[u]<now.fr) continue;
for(ll i=0;i<G[u].size();i++){
edge &e=G[u][i];
if(e.f>0&&dis[e.v]>dis[u]+e.c+h[u]-h[e.v]){
dis[e.v]=dis[u]+e.c+h[u]-h[e.v];
pv[e.v]=u,pe[e.v]=i;
Q.push(PII(dis[e.v],e.v));
}
}
}
if(dis[t]==inf) break;
for(ll i=0;i<=V;i++) h[i]+=dis[i];
newflow=Flow;
for(ll x=t;x!=s;x=pv[x]) newflow=min(newflow,G[pv[x]][pe[x]].f);
Flow-=newflow,flow+=newflow,cost+=newflow*h[t];
for(ll x=t;x!=s;x=pv[x]){
edge &e=G[pv[x]][pe[x]];
e.f-=newflow;
G[x][e.r].f+=newflow;
}
}
return {flow,cost};
}
}A;
int main(){
ll n,m;
while(~scanf("%lld%lld",&n,&m)){
ll s=1,t=n;
A.init(n);
for(ll i=1;i<=m;i++){
ll u,v,c;
scanf("%lld%lld%lld",&u,&v,&c);
A.add(u,v,1,c);
}
PII Ans=A.dinic(s,t,inf);
}
}
2.常用模型
(1)二分图上的最大流
简单,建超级源点与超级汇点,连上边,边权容量为1,很明显最大匹配数就是最大流。
(2)多源汇最大流
简单,建超级源点与超级汇点,分别所有源点,与汇点,边权无限,跑最大流。
(3)拆点
中等,当点上有点权控制流量时,而跑普通的最大流无法满足点权的限制,此时将点视为边,即将点拆分成两点,增加一条边出来控制流量,比较考验建图能力。
(4)最小割
中等,一张图的最小割等于其最大流,很多题目较抽象,无法理解成求最大流,但能转换成最小割,便可通过求最大流算出最小割。、
(5)上下界的网络
困难,抽象难想,不好建模。
将所有边分成两条,容量分别是下界,上界-下界。
下界的边是必须满足的,故将下界边的出点与汇点连,入点与源点连。
源点可以提供无限流量,让所有下界都充满水,检查汇点,若汇点流出的值小于所有下界的和,那么说明下界管子没流满水,就无可行流,反之合法。
加汇点连一条去源点的边,便变成了无向图,按上方法求可行流,在求源汇最大流,即在满足可行流的情况下求最大流,注意去除新源汇的反边流量防止其退流,使其变成非可行流。
和上题一样,最后只要求汇点到源点的最大流,看能退多少流回去就行。
(6)最大权闭合子图
困难,公式推导太抽象,这里只说我的理解。
将负权点连向汇点,容量为点权*(-1)。
将正权点连向源点,容量为点权。
正负点权间边,容量无限。
考虑最小割,很明显只会切源点与正点的连线,或汇点与负点的连线。
首先,先假设正权的点我全要,此时我便要支付必须要选的负点的费用。
而最小割,若切在负汇边上,说明我支付了该负点的费用。
若切在正源边上,说明要获取此正点太亏,把正点权还回去实现反悔。
所以答案就所有正点权和减去最小割。