当前位置: 首页 > news >正文

网络流的各种模型+题单

文章目录

  • 前言
  • 网络流模板
    • EK算法
    • dinic算法
    • Dinic封装版
  • 应用
  • 二分图匹配
      • P2756 飞行员配对方案问题(二分图模板)
      • P3254圆桌问题(类二分图匹配+最大流)
    • 关于行和列的二分图匹配问题
    • P6931 [ICPC 2017 WF] Mission Improbable
    • P2825 [HEOI2016/TJOI2016] 游戏
  • 无源汇上下界可行流
  • 有源汇上下界最大流
  • 有源汇上下界最小流
  • 多源汇最大流
  • 最大流之关键边
    • 关键边模板POJ3204 Ikki's Story I - Road Reconstruction
  • 最大流判定
    • P1674 [USACO05FEB] Secret Milking Machine G(秘密挤奶机,网络流+二分)
  • 分层图建图
    • P2754 [CTSC1999] 家园 / 星际转移问题(分层图建图)
  • 最大流拆点
    • P2891 [USACO07OPEN] Dining G
  • 杂题1
  • POJ3498/UVA12125 企鹅游行
    • SP4063/POJ1149 PIGS
  • 最小割
    • ZOJ2676 Networks Wars(01分数规划+最小割)
    • SP839 OPTM - Optimal Marks
  • 最小割之最大权闭合图
    • P4174 [NOI2006] 最大获利
  • 最小割之最大密度子图
    • 模板题:UVA1389 Hard Life
  • 最小割之最小权点覆盖
    • POJ2125 Destroying The Graph
  • 最小割之最大权独立集
    • P4474王者之剑
    • SP300 CABLETV - Cable TV Network(最小割拆点)
    • P2762 太空飞行计划问题(最大权闭合子图)
    • P3355 骑士共存问题(最大权独立集)
  • 费用流(所有最大流中的费用最大值/最小值)
  • 最小费用最大流
    • EK模板
  • 多路增广费用流模板(效率与zkw相当)
    • P4015 运输问题(最小+最大)
    • 负载平衡问题
    • P4014 分配问题(二分图最优匹配)
    • P4013 数字梯形问题(拆点+费用流)
  • 费用流之网格数问题
    • P2045 方格取数加强版(K取方格数)
    • P4012 深海机器人问题
    • P1251 餐巾计划问题
  • 有源汇上下界费用流
  • 费用流杂题
    • P3980 [NOI2008] 志愿者招募
  • 模拟费用流
    • AGC018Coins
    • BZOJ4977 跳伞
  • CF730I
  • 2024百度之星决赛 旅行商
  • XCPC网络流题目
    • 2024ICPC第一场网络赛H

前言

一年前看acwing学的网络流,把以前做的笔记都搬过来顺便复习一下基础。同时计划补补XCPC出现过的网络流题目
大部分的题目都可以在洛谷等平台找到

网络流模板

EK算法

属于单路增广

  1. bfs找增广路
  2. 更新残留网络(正向减去f,反向加f,总流加f)
  3. 用链式前向星存图,连续存正反边,则异或即为反边

复杂度 O ( n m 2 ) O(nm^2) O(nm2),处理 1 0 3 − 1 0 4 10^3-10^4 103104的数据

const int N=210,M=10010;
int n,m,S,T;//S为源点,T为汇点
int e[M],h[N],ne[M],idx;//链式前向星
int f[M],d[N],pre[N];//f存流量,d存增广路的最小值,pre求前驱路径的idx
void add(int a,int b,int c){e[idx]=b,f[idx]=c,ne[idx]=h[a],h[a]=idx++;e[idx]=a,f[idx]=0,ne[idx]=h[b],h[b]=idx++;//反向流量初始为0
}   
bool bfs(){queue<int> q;memset(vis,0,sizeof vis);q.push(S);vis[S]=1;d[S]=INF;while(!q.empty()){auto u=q.front();q.pop();for(int i=h[u];~i;i=ne[i]){int v=e[i];if(!vis[v]&&f[i]){vis[v]=1;d[v]=min(d[u],f[i]);pre[v]=i;if(v==T)return true;q.push(v);}}}return false;
}
int EK(){int r=0;while(bfs()){r+=d[T];for(int i=T;i!=S;i=e[pre[i]^1]){f[pre[i]]-=d[T],f[pre[i]^1]+=d[T];}}return r;
}
void solve(){cin>>n>>m>>S>>T;memset(h,-1,sizeof h);for(int i=1;i<=m;i++){int u,v,w;cin>>u>>v>>w;add(u,v,w);}cout<<EK();
}   

dinic算法

  1. 对EK算法的优化,dfs将所有增广路同时遍历
  2. 因为可能有环,因此需要bfs预处理一遍深度,分层遍历
  3. 对于一个节点x,当它在DFS中走到了第i条弧时,前i−1条弧到汇点的流一定已经被流满而没有可行的路线了那么当下一次再访问x节点时,前i−1条就没有任何意义了

复杂度 O ( n 2 m ) O(n^2m) O(n2m),处理 1 0 4 − 1 0 5 10^4-10^5 104105的数据

const int N=210,M=10010;
int n,m,S,T;//S为源点,T为汇点
int e[M],h[N],ne[M],idx;//链式前向星
int f[M],d[N],cur[N];//f存流量,d存层数,cur为弧优化,表示当前从哪条节点开始搜
void add(int a,int b,int c){e[idx]=b,f[idx]=c,ne[idx]=h[a],h[a]=idx++;e[idx]=a,f[idx]=0,ne[idx]=h[b],h[b]=idx++;//反向流量初始为0
}   
bool bfs(){queue<int> q;memset(d,-1,sizeof d);q.push(S);d[S]=0,cur[S]=h[S];while(!q.empty()){auto u=q.front();q.pop();for(int i=h[u];~i;i=ne[i]){int v=e[i];if(d[v]==-1&&f[i]){d[v]=d[u]+1;cur[v]=h[v];//一开始所有边都可以搜if(v==T)return true;q.push(v);}}}return false;
}
int find(int u,int limit){//深搜,limit表示从源点流向u的最多能流的流,flow表示已经流向汇点多少流if(u==T)return limit;int flow=0;for(int i=cur[u];~i&&flow<limit;i=ne[i]){//flow<limit优化很重要,否则易tleint v=e[i];cur[u]=i;//弧优化更新if(d[v]==d[u]+1&&f[i]){int t=find(v,min(f[i],limit-flow));if(!t)d[v]=-1;//如果到达不了T,标记下次就不会走到f[i]-=t,f[i^1]+=t,flow+=t;}}   return flow;
}
int dinic(){int r=0,flow;while(bfs())while(flow=find(S,INF))r+=flow;return r;
}
void solve(){cin>>n>>m>>S>>T;memset(h,-1,sizeof h);for(int i=1;i<=m;i++){int u,v,w;cin>>u>>v>>w;add(u,v,w);}cout<<dinic();
}  

Dinic封装版

struct MaxFlow{vector<int> e,h,ne;//链式前向星int n,idx,S,T;//S为源点,T为汇点vector<int> f,d,cur;//f存流量,d存层数,cur为弧优化,表示当前从哪条节点开始搜MaxFlow(){}MaxFlow(int n){init(n);}void init(int n,int S=0,int T=0){h.assign(n+1,-1);d.assign(n+1,0);cur.assign(n+1,0);e.clear();ne.clear();f.clear();idx=0;this->n=n;this->S=S;this->T=T==0?n:T;}void newEdge(int x=2){for(int i=0;i<x;i++){e.emplace_back();ne.emplace_back();f.emplace_back();}}void addEdge(int a,int b,int c){newEdge();e[idx]=b,f[idx]=c,ne[idx]=h[a],h[a]=idx++;e[idx]=a,f[idx]=0,ne[idx]=h[b],h[b]=idx++;//反向流量初始为0}bool bfs(){queue<int> q;d.assign(n+1,-1);q.push(S);d[S]=0,cur[S]=h[S];while(!q.empty()){auto u=q.front();q.pop();for(int i=h[u];~i;i=ne[i]){int v=e[i];if(d[v]==-1&&f[i]){d[v]=d[u]+1;cur[v]=h[v];//一开始所有边都可以搜if(v==T)return true;q.push(v);}}}return false;}int find(int u,int limit){//深搜,limit表示从源点流向u的最多能流的流,flow表示已经流向汇点多少流if(u==T)return limit;int flow=0;for(int i=cur[u];~i&&flow<limit;i=ne[i]){//flow<limit优化很重要,否则易tleint v=e[i];cur[u]=i;//弧优化更新if(d[v]==d[u]+1&&f[i]){int t=find(v,min(f[i],limit-flow));if(!t)d[v]=-1;//如果到达不了T,标记下次就不会走到f[i]-=t,f[i^1]+=t,flow+=t;}}   return flow;}int dinic(){int r=0,flow;while(bfs())while(flow=find(S,INF))r+=flow;return r;}
};

应用

二分图匹配

  • 复杂度 O ( m n ) O(m \sqrt n) O(mn )
  • 将源点S与左边点连一条边,右边点与汇点T连一条边,所有边的容量均为1
  • 证明:显然满足容量限制和流量守恒
    • 左边点如果被选,流入容量为1,那么流出容量也只能是1即只能连一条边到右边点;右边点同理
  • 最大流=最大二分图匹配

P2756 飞行员配对方案问题(二分图模板)

const int N=210,M=10010;
int n,m,S,T;
int h[N],e[M],ne[M],f[M],idx;
int d[N],cur[N];
void add(int a,int b,int c){e[idx]=b,f[idx]=c,ne[idx]=h[a],h[a]=idx++;e[idx]=a,f[idx]=0,ne[idx]=h[b],h[b]=idx++;
}
bool bfs(){queue<int> q;q.push(S);memset(d,-1,sizeof d);d[S]=0,cur[S]=h[S];while(!q.empty()){auto u=q.front();q.pop();for(int i=h[u];~i;i=ne[i]){int v=e[i];if(d[v]==-1&&f[i]){cur[v]=h[v];d[v]=d[u]+1;if(v==T)return true;q.push(v);}}}return false;
}
int find(int u,int limit){if(u==T)return limit;int flow=0;for(int i=cur[u];~i&&flow<limit;i=ne[i]){int v=e[i];cur[u]=i;if(d[v]==d[u]+1&&f[i]){int t=find(v,min(f[i],limit-flow));if(!t)d[v]=-1;f[i]-=t,f[i^1]+=t,flow+=t;}}return flow;
}
int dinic(){int r=0,flow;while(bfs())while(flow=find(S,INF))r+=flow;return r;
}
void solve(){memset(h,-1,sizeof h);cin>>m>>n;S=0,T=n+1;for(int i=1;i<=m;i++)add(S,i,1);for(int i=m+1;i<=n;i++)add(i,T,1);int u,v;while(cin>>u>>v,u!=-1)add(u,v,1);cout<<dinic()<<"\n";for(int i=0;i<idx;i+=2){if(e[i]>m&&e[i]<=n&&!f[i]){cout<<e[i^1]<<" "<<e[i]<<"\n";}}
}

P3254圆桌问题(类二分图匹配+最大流)

题意:
m个不同单位,派出r_i个代表,在n个圆桌聚餐,不希望看到同一单位在同一圆桌聚餐,求是否有方案数
圆桌不一定坐满
思路:

  • 网络流建图,源点向单位建边,容量为r_i
  • 圆桌向汇点建边,容量为c_i
  • 单位对每个圆桌建边,容量为1
  • 则每个单位流入r_i,单位向一个圆桌只能流向一个人(满足题意)
  • 判断满流==人数
  • 方案数枚举:
    • v为圆桌点就输出
void solve(){memset(h,-1,sizeof h);cin>>m>>n;S=0,T=m+n+1;int tot=0;for(int i=1;i<=m;i++){int c;cin>>c;tot+=c;add(S,i,c);}for(int i=1;i<=n;i++){int c;cin>>c;add(m+i,T,c);}for(int i=1;i<=m;i++){for(int j=1;j<=n;j++)add(i,m+j,1);}if(dinic()!=tot){cout<<0;return;}cout<<1<<"\n";for(int i=1;i<=m;i++){for(int j=h[i];~j;j=ne[j]){if(e[j]>m&&e[j]<=n+m&&!f[j]){cout<<e[j]-m<<" ";}}cout<<"\n";}
}  

关于行和列的二分图匹配问题

这类问题通常和二分图最大匹配有关

P6931 [ICPC 2017 WF] Mission Improbable

n*m的地方摆放着正方体箱子, a i , j a_{i,j} ai,j表示这个区域的箱子数量

需要拿走尽可能的箱子,三视图不变

对于俯视图,如果这个位置有箱子,那么可以拿走只剩一个箱子

对于侧视和正视,我们算出行和列的最大值,把箱子摆回去对应的高度

但是还可以节省,如果 i i i行最大值和 j j j列最大值相同,那么只用摆一个高度即可

相当于是行和列的匹配,二分图求一个最大匹配

int vis[210],match[210],ts;
int a[110][110],L[110],R[110];
vector<int> adj[210];
bool find(int u){if(vis[u]==ts)return false;vis[u]=ts;for(auto v:adj[u]){if(!match[v]||find(match[v])){match[v]=u;return true;}}return false;
}
void solve() {int n,m;cin>>n>>m;int ans=0;for(int i=1;i<=n;i++){for(int j=1;j<=m;j++){cin>>a[i][j];if(a[i][j])ans+=a[i][j]-1;L[i]=max(L[i],a[i][j]);R[j]=max(R[j],a[i][j]);}}for(int i=1;i<=n;i++){if(L[i])ans-=L[i]-1;}for(int i=1;i<=m;i++){if(R[i])ans-=R[i]-1;}for(int i=1;i<=n;i++){for(int j=1;j<=m;j++){if(L[i]==R[j]&&a[i][j]){adj[i].push_back(j+n);}}}for(int i=1;i<=n;i++){ts++;if(find(i))ans+=L[i]-1;}cout<<ans<<"\n";
}

P2825 [HEOI2016/TJOI2016] 游戏

n*m网格放炸弹,每次会把行和列清空,除非遇到墙,墙会阻止炸弹传播

求最大炸弹数

将行列编号拆分即可,比如**#*,那么赋予***为两个不同的行,求二分图最大匹配

无源汇上下界可行流

n个点,m条边有上下界满足流量守恒的可行方案

  • 核心 ( G , f ) − > ( G ′ , f ′ ) 核心 (G,f)->(G',f') 核心(G,f)>(G,f)
  • c L ( u , v ) < = f ( u , v ) < = c H ( u , v ) 转化成 0 < = f ( u , v ) − c L ( u , v ) < = c H ( u , v ) − c L ( u , v ) c_L(u,v)<=f(u,v)<=c_H(u,v) 转化成0<=f(u,v)-c_L(u,v)<=c_H(u,v)-c_L(u,v) cL(u,v)<=f(u,v)<=cH(u,v)转化成0<=f(u,v)cL(u,v)<=cH(u,v)cL(u,v)
  • c i n 为少进的流量, c o u t 为少出的流量 c_{in}为少进的流量,c_{out}为少出的流量 cin为少进的流量,cout为少出的流量
    • 源图流量守恒
    • c i n > c o u t , 即少进了 c i n − c o u t 的流量,让源点 S 连一条边补上 c_{in}>c_{out} ,即少进了c_{in}-c_{out}的流量,让源点S连一条边补上 cin>cout,即少进了cincout的流量,让源点S连一条边补上
    • c i n < c o u t , 向汇点连边 c_{in}<c_{out},向汇点连边 cin<cout,向汇点连边
  • 看方案是否有只需看是否满流
const int N=500,M=100010;
int n,m,S,T;
int h[N],e[M],f[M],l[M],ne[M],idx;
int d[N],cur[N],A[N];//A[i]表示i的少入还是少出
void add(int a,int b,int c,int d){e[idx]=b,f[idx]=d-c,l[idx]=c,ne[idx]=h[a],h[a]=idx++;e[idx]=a,f[idx]=0,ne[idx]=h[b],h[b]=idx++;
}
bool bfs(){queue<int> q;memset(d,-1,sizeof d);q.push(S);d[S]=0,cur[S]=h[S];while(!q.empty()){auto u=q.front();q.pop();for(int i=h[u];~i;i=ne[i]){int v=e[i];if(d[v]==-1&&f[i]){d[v]=d[u]+1;cur[v]=h[v];if(v==T)return true;q.push(v);}}}return false;
}
int find(int u,int limit){if(u==T)return limit;int flow=0;for(int i=cur[u];~i&&flow<limit;i=ne[i]){int v=e[i];cur[u]=i;if(d[v]==d[u]+1&&f[i]){int t=find(v,min(f[i],limit-flow));if(!t)d[v]=-1;f[i]-=t,f[i^1]+=t,flow+=t;}}return flow;
}
int dinic(){int r=0,flow;while(bfs())while(flow=find(S,INF))r+=flow;return r;
}
void solve(){cin>>n>>m;S=0,T=n+1;memset(h,-1,sizeof h);for(int i=1;i<=m;i++){int a,b,c,d;cin>>a>>b>>c>>d;add(a,b,c,d);A[a]-=c,A[b]+=c;}int tot=0;for(int i=1;i<=n;i++){if(A[i]>0)add(S,i,0,A[i]),tot+=A[i];else add(i,T,0,-A[i]);}if(dinic()!=tot){cout<<"NO\n";return;}cout<<"YES\n";for(int i=0;i<2*m;i+=2){cout<<f[i^1]+l[i]<<"\n";//当前边满流等于残余网络的反向边流量}
}   

有源汇上下界最大流

  • 源网络 s 和 t 不满足流量守恒 , 将 t 连一条 + ∞ 的边到 s , 就变成无源汇上下界可行流,建新图 源网络s和t不满足流量守恒,将t连一条+∞的边到s,就变成无源汇上下界可行流,建新图 源网络st不满足流量守恒,t连一条+的边到s,就变成无源汇上下界可行流,建新图
  • 则对新图的 s ( 原网络的源点 ) 求到 t ( 原网络的汇点 ) 求增广路,没有则表示源网络最大流 则对新图的s(原网络的源点)求到t(原网络的汇点)求增广路,没有则表示源网络最大流 则对新图的s(原网络的源点)求到t(原网络的汇点)求增广路,没有则表示源网络最大流
    • 证明: f < = > f ′ ( s , t ) f <=> f'(s,t) f<=>f(s,t)
    • 源网络的 f 对应新网络的满流 f ′ 源网络的f对应新网络的满流f' 源网络的f对应新网络的满流f
      • ∣ f ′ + f ′ ( s , t ) ∣ 由于 f ′ 已满流, S 不会有多余的流量流向 s ,剩余流量也进不去 T ,因此仍然是个满流即对应源网络的一个可行流 |f'+f'(s,t)| 由于f'已满流,S不会有多余的流量流向s,剩余流量也进不去T,因此仍然是个满流即对应源网络的一个可行流 f+f(s,t)由于f已满流,S不会有多余的流量流向s,剩余流量也进不去T,因此仍然是个满流即对应源网络的一个可行流
      • 假设有一个 f 对应新网络的 f ′ , 另有一个新网络的流 f 0 ′ 和 f 0 ′ ( s , t ) 则 ∣ f ′ − f 0 ′ ∣ , 满流减满流就只剩下除 S , T 的点可以考虑 , 对应 f 0 ′ ( s , t ) 假设有一个f对应新网络的f',另有一个新网络的流f_0'和f'_0(s,t) 则|f'-f'_0|,满流减满流就只剩下除S ,T的点可以考虑,对应f'_0(s,t) 假设有一个f对应新网络的f,另有一个新网络的流f0f0(s,t)ff0,满流减满流就只剩下除S,T的点可以考虑,对应f0(s,t)
    • 因此 f < = > f ′ ( s , t ) 因此f<=>f'(s,t) 因此f<=>f(s,t)
    • 对于一个可行流 f ,新图存在一个满流 f ′ 与之对应, f ′ 是一个定值, f ′ 对应的 s − > t 的流量记为 f 0 ′ ( s , t ) 也是定值,要使 f ( s , t ) = ∣ f 0 ′ ( s , t ) + f 增 ( s , t ) ∣ 最大就要增广 ( s , t ) 对于一个可行流f,新图存在一个满流f'与之对应,f'是一个定值,f'对应的s->t的流量记为f'_0(s,t)也是定值,要使f(s,t)=|f'_0(s,t)+f_增(s,t)|最大就要增广(s,t) 对于一个可行流f,新图存在一个满流f与之对应,f是一个定值,f对应的s>t的流量记为f0(s,t)也是定值,要使f(s,t)=f0(s,t)+f(s,t)最大就要增广(s,t)
  • 完整过程:
    1. t->s连边
    2. 无源上下界做法求最大流
    3. 删边t->s
    4. 残留网络求s->t最大流
    5. 若求的是最小流,则求t->s的最大流
    6. 最大流就是满流时 t − > s ( 中间节点流量守恒,等于 s − > t ) 的流量 + 增广流量 最大流就是满流时t->s(中间节点流量守恒,等于s->t)的流量+增广流量 最大流就是满流时t>s(中间节点流量守恒,等于s>t)的流量+增广流量
int n,m,S,T,s,t;
int h[N],e[M],f[M],ne[M],idx;
int d[N],cur[N],A[N];
void add(int a,int b,int c,int d){e[idx]=b,f[idx]=d-c,ne[idx]=h[a],h[a]=idx++;e[idx]=a,f[idx]=0,ne[idx]=h[b],h[b]=idx++;
}
bool bfs(){queue<int> q;memset(d,-1,sizeof d);d[S]=0,cur[S]=h[S];q.push(S);while(!q.empty()){auto u=q.front();q.pop();for(int i=h[u];~i;i=ne[i]){int v=e[i];if(d[v]==-1&&f[i]){d[v]=d[u]+1;cur[v]=h[v];if(v==T)return true;q.push(v);}}}return false;
}
int find(int u,int limit){if(u==T)return limit;int flow=0;for(int i=cur[u];~i&&flow<limit;i=ne[i]){cur[u]=i;int v=e[i];if(d[v]==d[u]+1&&f[i]){int t=find(v,min(f[i],limit-flow));if(!t)d[v]=-1;f[i]-=t,f[i^1]+=t,flow+=t;}}return flow;
}
int dinic(){int r=0,flow;while(bfs())while(flow=find(S,INF))r+=flow;return r;
}
void solve(){cin>>n>>m>>s>>t;memset(h,-1,sizeof h);S=0,T=n+1;while(m--){int a,b,c,d;cin>>a>>b>>c>>d;add(a,b,c,d);A[a]-=c,A[b]+=c;}int tot=0;for(int i=1;i<=n;i++){if(A[i]>0)add(S,i,0,A[i]),tot+=A[i];else add(i,T,0,-A[i]);}add(t,s,0,INF);if(dinic()!=tot){cout<<"please go home to sleep\n";return;}int res=f[idx-1];//t->s的流量S=s,T=t;f[idx-2]=f[idx-1]=0;//流量为0,删边cout<<res+dinic()<<"\n";
}   

有源汇上下界最小流

与有源汇上下界最大流类似,只不过求的是反向增广流,最小流=原可行流-反向增广流

const int N=50010,M=(N+125003)*2;
int n,m,S,T,s,t;
int h[N],e[M],f[M],ne[M],idx;
int d[N],cur[N],A[N];
void add(int a,int b,int c,int d){e[idx]=b,f[idx]=d-c,ne[idx]=h[a],h[a]=idx++;e[idx]=a,f[idx]=0,ne[idx]=h[b],h[b]=idx++;
}
bool bfs(){queue<int> q;memset(d,-1,sizeof d);d[S]=0,cur[S]=h[S];q.push(S);while(!q.empty()){auto u=q.front();q.pop();for(int i=h[u];~i;i=ne[i]){int v=e[i];if(d[v]==-1&&f[i]){d[v]=d[u]+1;cur[v]=h[v];if(v==T)return true;q.push(v);}}}return false;
}
int find(int u,int limit){if(u==T)return limit;int flow=0;for(int i=cur[u];~i&&flow<limit;i=ne[i]){cur[u]=i;int v=e[i];if(d[v]==d[u]+1&&f[i]){int t=find(v,min(f[i],limit-flow));if(!t)d[v]=-1;f[i]-=t,f[i^1]+=t,flow+=t;}}return flow;
}
int dinic(){int r=0,flow;while(bfs())while(flow=find(S,INF))r+=flow;return r;
}
void solve(){cin>>n>>m>>s>>t;memset(h,-1,sizeof h);S=0,T=n+1;while(m--){int a,b,c,d;cin>>a>>b>>c>>d;add(a,b,c,d);A[a]-=c,A[b]+=c;}int tot=0;for(int i=1;i<=n;i++){if(A[i]>0)add(S,i,0,A[i]),tot+=A[i];else add(i,T,0,-A[i]);}add(t,s,0,INF);if(dinic()!=tot){cout<<"please go home to sleep\n";return;}int res=f[idx-1];//t->s的流量S=t,T=s;f[idx-2]=f[idx-1]=0;//流量为0,删边cout<<res-dinic()<<"\n";
}   

多源汇最大流

建一个虚拟源点S和汇点T,S向原源点连∞边,汇点向T连∞边,跑dinic即可

最大流之关键边

在流网络中,只能改变一条边的容量,使最大流增加
关键边必定是满流的
在 G f 中存在 S − > u 且 v − > T 的 > 0 的路径 在G_f中存在S->u且v->T的>0的路径 Gf中存在S>uv>T>0的路径

关键边模板POJ3204 Ikki’s Story I - Road Reconstruction

int n,m,S,T;
int h[N],e[M],f[M],ne[M],idx;
int d[N],cur[N];
bool vis_s[N],vis_t[N];
void add(int a,int b,int c){e[idx]=b,f[idx]=c,ne[idx]=h[a],h[a]=idx++;e[idx]=a,f[idx]=0,ne[idx]=h[b],h[b]=idx++;
}
bool bfs(){queue<int> q;memset(d,-1,sizeof d);d[S]=0,cur[S]=h[S];q.push(S);while(!q.empty()){int u=q.front();q.pop();for(int i=h[u];~i;i=ne[i]){int v=e[i];if(d[v]==-1&&f[i]){d[v]=d[u]+1;cur[v]=h[v];if(v==T)return true;q.push(v);}}}return false;
}
int find(int u,int limit){if(u==T)return limit;int flow=0;for(int i=cur[u];~i&&flow<limit;i=ne[i]){cur[u]=i;int v=e[i];if(d[v]==d[u]+1&&f[i]){int t=find(v,min(f[i],limit-flow));if(!t)d[v]=-1;f[i]-=t,f[i^1]+=t,flow+=t;}}return flow;
}
int dinic(){int r=0,flow;while(bfs())while(flow=find(S,INF))r+=flow;return r;
}
void dfs(int u,bool st[],int t){st[u]=1;for(int i=h[u];~i;i=ne[i]){int j=i^t,v=e[i];//寻找路径,如果是从T开始找,那么应该判断反向边是否满流if(f[j]&&!st[v])dfs(v,st,t);}
}
void solve(){cin>>n>>m;memset(h,-1,sizeof h);S=0,T=n-1;for(int i=0;i<m;i++){int a,b,c;cin>>a>>b>>c;add(a,b,c);}dinic();dfs(S,vis_s,0);dfs(T,vis_t,1);int res=0;for(int i=0;i<2*m;i+=2){if(!f[i]&&vis_s[e[i^1]]&&vis_t[e[i]]){res++;}}cout<<res<<"\n";
}   

最大流判定

P1674 [USACO05FEB] Secret Milking Machine G(秘密挤奶机,网络流+二分)

题意:
n个点,m条边,每条边(有长度)只能走一次,问从1-n能走t次的情况下,最长路最短
思路:
二分答案,将大于mid的边删掉,则题目转化为:

  • 给定一张流网络,最大流是否大于等于t
int n,m,S,T,t;
int h[N],e[M],f[M],ne[M],w[M],idx;
int d[N],cur[N];
void add(int a,int b,int c){e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;e[idx]=a,w[idx]=c,ne[idx]=h[b],h[b]=idx++;
}
bool bfs(){queue<int> q;memset(d,-1,sizeof d);d[S]=0,cur[S]=h[S];q.push(S);while(!q.empty()){int u=q.front();q.pop();for(int i=h[u];~i;i=ne[i]){int v=e[i];if(d[v]==-1&&f[i]){d[v]=d[u]+1;cur[v]=h[v];if(v==T)return true;q.push(v);}}}return false;
}
int find(int u,int limit){if(u==T)return limit;int flow=0;for(int i=cur[u];~i&&flow<limit;i=ne[i]){cur[u]=i;int v=e[i];if(d[v]==d[u]+1&&f[i]){int t=find(v,min(f[i],limit-flow));if(!t)d[v]=-1;f[i]-=t,f[i^1]+=t,flow+=t;}}return flow;
}
int dinic(){int r=0,flow;while(bfs())while(flow=find(S,INF))r+=flow;return r;
}
bool check(int x){for(int i=0;i<idx;i++){if(w[i]>x)f[i]=0;else f[i]=1;}return dinic()>=t;
}
void solve(){cin>>n>>m>>t;memset(h,-1,sizeof h);S=1,T=n;for(int i=0;i<m;i++){int a,b,c;cin>>a>>b>>c;add(a,b,c);}int l=1,r=1e6;while(l<r){int mid=l+r>>1;if(check(mid))r=mid;else l=mid+1;}cout<<l<<"\n";
}   

分层图建图

P2754 [CTSC1999] 家园 / 星际转移问题(分层图建图)

  • 题意
    • n个空间站,m搜太空船,地球编号0,月球为-1,太空船将周期性地停靠太空站,每转移一次耗费1个时间,问转移所有人的时间
  • 思路
    • 并查集判断是否能从地球到月球

    • 网络流只与流量有关而与距离无关,要使二者产生关联应该建立分层图,每一天的每个太空站为一层,如下图所示(飞船相当于边)

    • $ 第0天如果有飞船,则与S连容量为h_i(飞船限载人数)的边 $

    • $ 所有天数的n+1的点向T连+∞的边 $

    • $ 如果飞船能到下一个空间站,则向下一天的下一个站连边,容量为h_i $

    • $ 飞船也可以停留一晚,向下一天的同一点连+∞的边 $

    • 可以从0开始枚举天数,在增广路增加点继续增广

int h[N],e[M],ne[M],f[M],idx;
int d[N],cur[N];
int n,m,k,S,T;
void add(int a,int b,int c){e[idx]=b,f[idx]=c,ne[idx]=h[a],h[a]=idx++;e[idx]=a,f[idx]=0,ne[idx]=h[b],h[b]=idx++;
}
bool bfs(){queue<int> q;memset(d,-1,sizeof d);q.push(S);d[S]=0,cur[S]=h[S];while(!q.empty()){auto u=q.front();q.pop();for(int i=h[u];~i;i=ne[i]){int v=e[i];if(d[v]==-1&&f[i]){d[v]=d[u]+1;cur[v]=h[v];if(v==T)return true;q.push(v);}}}return false;
}
int find(int u,int limit){if(u==T)return limit;int flow=0;for(int i=cur[u];~i;i=ne[i]){cur[u]=i;int v=e[i];if(d[v]==d[u]+1&&f[i]){int t=find(v,min(f[i],limit-flow));if(!t)d[v]=-1;f[i]-=t,f[i^1]+=t,flow+=t;}}return flow;
}
int dinic(){int r=0,flow;while(bfs())while(flow=find(S,INF))r+=flow;return r;
}
struct Ship{int h,n;int id[30];
} ship[30];
int par[30];
int find(int x){return par[x]==x?x:par[x]=find(par[x]);
}
int get(int day,int x){return day*(n+2)+x;
}
void solve(){cin>>n>>m>>k;S=N-1,T=N-2;for(int i=0;i<=n+1;i++)par[i]=i;for(int i=0;i<m;i++){int a,b;cin>>a>>b;ship[i]={a,b};for(int j=0;j<b;j++){int id;cin>>id;if(id==-1)id=n+1;ship[i].id[j]=id;if(j){int x=ship[i].id[j-1];int fx=find(x),fy=find(id);if(fx!=fy)par[fx]=fy;}}}memset(h,-1,sizeof h);if(find(0)!=find(n+1)){cout<<"0\n";return;}add(S,get(0,0),k);add(get(0,n+1),T,INF);int day=1,res=0;while(true){add(get(day,n+1),T,INF);for(int i=0;i<=n+1;i++){add(get(day-1,i),get(day,i),INF);}for(int i=0;i<m;i++){auto [h,b,c]=ship[i];int x=c[(day-1)%b],y=c[day%b];add(get(day-1,x),get(day,y),h);}res+=dinic();if(res>=k)break;day++;}cout<<day<<"\n";
}

最大流拆点

P2891 [USACO07OPEN] Dining G

有食物饮料奶牛三种点,奶牛只吃一种食物和饮料(有些喜欢有些不喜欢,不喜欢则不连边),食物饮料只能用一次,问最多有几头牛能吃到食物喝饮料

  • 类比二分图正常连边的话会有问题
    • n(n>=2)种食物流进奶牛,n条边流向饮料,这是满足可行流的,但是不满足题意
    • 将奶牛拆分成两个点,每次只允许一种食物通过奶牛即可
const int N=410,M=41000;
int h[N],e[M],ne[M],f[M],idx;
int d[N],cur[N];
int n,F,D,S,T;
void add(int a,int b,int c){e[idx]=b,f[idx]=c,ne[idx]=h[a],h[a]=idx++;e[idx]=a,f[idx]=0,ne[idx]=h[b],h[b]=idx++;
}
bool bfs(){queue<int> q;memset(d,-1,sizeof d);q.push(S);d[S]=0,cur[S]=h[S];while(!q.empty()){auto u=q.front();q.pop();for(int i=h[u];~i;i=ne[i]){int v=e[i];if(d[v]==-1&&f[i]){d[v]=d[u]+1;cur[v]=h[v];if(v==T)return true;q.push(v);}}}return false;
}
int find(int u,int limit){if(u==T)return limit;int flow=0;for(int i=cur[u];~i&&flow<limit;i=ne[i]){cur[u]=i;int v=e[i];if(d[v]==d[u]+1&&f[i]){int t=find(v,min(f[i],limit-flow));if(!t)d[v]=-1;f[i]-=t,f[i^1]+=t,flow+=t;}}return flow;
}
int dinic(){int r=0,flow;while(bfs())while(flow=find(S,INF))r+=flow;return r;
}
void solve(){cin>>n>>F>>D;S=0,T=2*n+F+D+1;memset(h,-1,sizeof h);for(int i=1;i<=F;i++)add(S,n*2+i,1);for(int i=1;i<=D;i++)add(n*2+F+i,T,1);for(int i=1;i<=n;i++){add(i,n+i,1);int a,b,x;cin>>a>>b;while(a--){cin>>x;add(n*2+x,i,1);}while(b--){cin>>x;add(n+i,n*2+F+x,1);}}cout<<dinic()<<"\n";
}

杂题1

POJ3498/UVA12125 企鹅游行

题意:
一张二维图,给定n个点和企鹅的跳跃能力D,每个点上有ai只企鹅,bi次能用的起跳次数(超过就碎掉),问能否有一个点聚集所有的企鹅
思路:

  • 初始有多少企鹅就从S连多少的边
  • 枚举每个点能跳跃到的点,连正无穷边
  • 枚举一个点当汇点跑dinic
    • 还原流量f_i+=f_{i^1} f_{i^1}=0
const int N=210,M=21000;
int h[N],e[M],ne[M],f[M],idx;
int d[N],cur[N];
int n,S,T;
double D;
PII p[N];
void add(int a,int b,int c){e[idx]=b,f[idx]=c,ne[idx]=h[a],h[a]=idx++;e[idx]=a,f[idx]=0,ne[idx]=h[b],h[b]=idx++;
}
bool bfs(){queue<int> q;memset(d,-1,sizeof d);q.push(S);d[S]=0,cur[S]=h[S];while(!q.empty()){auto u=q.front();q.pop();for(int i=h[u];~i;i=ne[i]){int v=e[i];if(d[v]==-1&&f[i]){d[v]=d[u]+1;cur[v]=h[v];if(v==T)return true;q.push(v);}}}return false;
}
int find(int u,int limit){if(u==T)return limit;int flow=0;for(int i=cur[u];~i&&flow<limit;i=ne[i]){cur[u]=i;int v=e[i];if(d[v]==d[u]+1&&f[i]){int t=find(v,min(f[i],limit-flow));if(!t)d[v]=-1;f[i]-=t,f[i^1]+=t,flow+=t;}}return flow;
}
int dinic(){int r=0,flow;while(bfs())while(flow=find(S,INF))r+=flow;return r;
}
bool check(PII a,PII b){double x=a.x-b.x;double y=a.y-b.y;return x*x+y*y<D+1e-8;
}
void solve(){memset(h,-1,sizeof h);idx=0;cin>>n>>D;D*=D;S=0;int tot=0;for(int i=1;i<=n;i++){cin>>p[i].x>>p[i].y;int a,b;cin>>a>>b;add(S,i,a),tot+=a;add(i,n+i,b);}for(int i=1;i<=n;i++){for(int j=i+1;j<=n;j++){if(check(p[i],p[j])){add(n+i,j,INF);add(n+j,i,INF);}}}int cnt=0;for(int i=1;i<=n;i++){T=i;for(int j=0;j<idx;j+=2){f[j]+=f[j^1];f[j^1]=0;}if(dinic()==tot){cout<<i-1<<" ";cnt++;}}if(!cnt)cout<<"-1";cout<<"\n";
}

SP4063/POJ1149 PIGS

题意:
m个猪圈,n个顾客,每个顾客需要b只猪,顾客有猪圈的钥匙,每一个顾客来(按顺序)打开一些猪圈,拿走一些猪,然后可以重新分配开了的猪圈的猪,下一个顾客来之前把猪圈关上,问最多能卖多少只猪
思路:

  • 如果没有重新分配的条件,则题目转化为两组点求最大流
  • 如果没有顺序,那么从顾客反向连一条容量(为他能拿到所有的猪的数量)的边,求最大流
  • 有顺序做法:
    • 对于一个猪圈,如果它之前没有被开过,那么直接从源点连向顾客
    • 否则从上一个顾客连流量为无穷的边到现在的顾客

最小割

ZOJ2676 Networks Wars(01分数规划+最小割)

题意:
给定带权无向图 G = ( V , E ) ,每条边有容量 W e ,将点 s 和 t 分开的一个边割集 C ,使割集的平均边权最小,即最小化 ∑ e ∈ c w e ∣ C ∣ 给定带权无向图G=(V,E),每条边有容量W_e,将点s和t分开的一个边割集C,使割集的平均边权最小,即最小化 \frac{\sum _{e∈c}w_e }{|C|} 给定带权无向图G=(V,E),每条边有容量We,将点st分开的一个边割集C,使割集的平均边权最小,即最小化Cecwe
这里边割集指删去这些边, s 与 t 不再联通 这里边割集指删去这些边,s与t不再联通 这里边割集指删去这些边,st不再联通
思路:

  • 假设 ∑ e ∈ c w e ∣ C ∣ > λ 假设\frac{\sum _{e∈c}w_e }{|C|}>\lambda 假设Cecwe>λ
  • ∑ e ∈ c w e > ∣ C ∣ ⋅ λ 即 ∑ e ∈ c w e − ∣ C ∣ ⋅ λ > 0 即 ∑ e ∈ c ( w e − λ ) > 0 \sum _{e∈c}w_e>|C| \cdot \lambda 即\sum _{e∈c}w_e-|C| \cdot \lambda>0即\sum _{e∈c}(w_e- \lambda)>0 ecwe>CλecweCλ>0ec(weλ)>0
  • <亦是如此,因此可以二分
  • 这里的边割集可以是所有令s,t断开的边加上若干条无关边(集合就与流网络的边割集不一样了,即可以选集合S,T的内部边)
    • 对于w'[i]=w[i]-lambda,如果<=0则不管有没有关都选上,一定会让答案更小
    • 对于所有>0肯定是越少选越好,即不要选集合内部边,这就和流网络的集合对应上了,就可以用网络流来解
```c++
const int N=105,M=805;
const double eps=1e-8,inf=1e8;
int n,m,S,T;
int h[N],e[M],ne[M],w[M],idx;
double f[M];
int d[N],hh,tt,q[N],cur[N];
void add(int a,int b,int c)
{e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
bool bfs()
{memset(d,-1,sizeof d);d[S]=hh=tt=0;q[0]=S;cur[S]=h[S];while(hh<=tt){int x=q[hh++];for(int i=h[x];~i;i=ne[i]){int y=e[i];if(d[y]==-1&&f[i]){d[y]=d[x]+1;cur[y]=h[y];if(y==T)return true;q[++tt]=y;}}}return false;
}
double dfs(int x,double limit)
{if(x==T)return limit;double flow=0;for(int i=cur[x];~i&&flow<limit;i=ne[i]){cur[x]=i;int y=e[i];if(d[y]==d[x]+1&&f[i]){double t=dfs(y,min(f[i],limit-flow));if(!t)d[y]=-1;f[i]-=t,f[i^1]+=t,flow+=t;}}return flow;
}
bool dinic(double x)
{double res=0;for(int i=0;i<idx;i+=2)if(w[i]<=x)res+=w[i]-x,f[i]=f[i^1]=0;elsef[i]=f[i^1]=w[i]-x;double flow=0;while(bfs())while((flow=dfs(S,inf))>0)res+=flow;return res<=0;
}
int main()
{memset(h,-1,sizeof h);scanf("%d%d%d%d",&n,&m,&S,&T);for(int i=1;i<=m;i++){int x,y,z;scanf("%d%d%d",&x,&y,&z);add(x,y,z),add(y,x,z);}double l=1,r=1e7;while(fabs(r-l)>eps){double mid=(l+r)/2;if(dinic(mid))r=mid;elsel=mid;}printf("%.2lf",l);return 0;
}

SP839 OPTM - Optimal Marks

给你一个无向图G(V,E)。 每个顶点都有一个int范围内的整数的标记。 不同的顶点可能有相同的标记。

对于边(u,v),我们定义Cost(u,v)= mark [u] xor mark [v]。

现在我们知道某些节点的标记了。你需要确定其他节点的标记,以使边的总成本尽可能小。

如果有多解,请输出 ∑ m a r k i \sum mark_i marki最小的方案。如果仍有多解,输出任意一个。

思路:

  • 每一位互不影响,最终求和即可
  • mark为0/1,则可以划分01两个集合,转换为在该图求最小割容量(边权对应容量)
    • 对于初始为0的点,与源点连无穷边,则它一定不会形成割边(无法满流),1的点向汇点连无穷边
  • 然后如果问题求的最大值,应该是每一位dinic(i)<<i,但是这题题意求所有点的对应数
    • 那么如果当前点存在增广路到达Td[i]!=-1,则加上当前数位值
```c++
const int N=510,M=(3000+N*2)*2;
int h[N],e[M],ne[M],f[M],idx;
int d[N],cur[N],p[N],ans[N];
int n,m,S,T;
PII edges[3010];
void add(int a,int b,int c,int d){e[idx]=b,f[idx]=c,ne[idx]=h[a],h[a]=idx++;e[idx]=a,f[idx]=d,ne[idx]=h[b],h[b]=idx++;
}
bool bfs(){queue<int> q;memset(d,-1,sizeof d);q.push(S);d[S]=0,cur[S]=h[S];while(!q.empty()){auto u=q.front();q.pop();for(int i=h[u];~i;i=ne[i]){int v=e[i];if(d[v]==-1&&f[i]){d[v]=d[u]+1;cur[v]=h[v];if(v==T)return true;q.push(v);}}}return false;
}
int find(int u,int limit){if(u==T)return limit;int flow=0;for(int i=cur[u];~i&&flow<limit;i=ne[i]){cur[u]=i;int v=e[i];if(d[v]==d[u]+1&&f[i]){int t=find(v,min(f[i],limit-flow));if(!t)d[v]=-1;f[i]-=t,f[i^1]+=t,flow+=t;}}return flow;
}
int dinic(){int r=0,flow;while(bfs())while(flow=find(S,INF))r+=flow;return r;
}
void build(int k){memset(h,-1,sizeof h);S=0,T=n+1;idx=0;for(int i=1;i<=n;i++){if(p[i]>=0){if(p[i]>>k&1)add(S,i,INF,0);else add(i,T,INF,0);}}for(int i=1;i<=m;i++)add(edges[i].x,edges[i].y,1,1);dinic();for(int i=1;i<=n;i++){if(d[i]!=-1)ans[i]|=(1ll<<k);}
}
void solve(){memset(p,-1,sizeof p);memset(ans,0,sizeof ans);cin>>n>>m;for(int i=1;i<=m;i++)cin>>edges[i].x>>edges[i].y;int k;cin>>k;while(k--){int x,y;cin>>x>>y;p[x]=y;}for(int i=0;i<31;i++)build(i);for(int i=1;i<=n;i++)cout<<ans[i]<<"\n";
}

最小割之最大权闭合图

闭合子图:一个点集,点的所有边都指向点的内部,然后包含这些点的所有边
最大权闭合图:集合内的点权总和最大
闭合图内任意点的任意后继也一定还在闭合图中。

物理意义:事物间依赖关系:一个事件要发生,它需要的所有前提也都一定要发生。

  • 建图,对于原图的边全用无穷边代替
    • 对于点权大于等于0的点,与S相连
    • 对于点权小于0的点,与T相连
    • 边权均为绝对值
  • 定义简单割:流网络中,割集中的边只会在与S,T相连的边上
    • 证明:闭合子图<=>简单割
    • 对于一个闭合子图的点集v1来讲,对应于流网络的点集v1+S,那么点集的点不会走到另外一个集合,因此割边不会出现在原图的边中,即一个简单割
    • 反之亦然
  • 计算简单割的流量C(S,T),这里源点和汇点为s,t
    • S=v1+s,T=v2+t则有四种边
      • v1->v2因为简单割割边不会出现在原图的边上,所以这种边不存在
      • s->v2,存在
      • v1->t,存在
      • s->t,显然不存在
    • 因此 C ( S , T ) = C ( s , v 2 ) + C ( v 1 , t ) = ∑ v ∈ v 2 + w v + ∑ v ∈ v 1 − ( − w v ) 因此C(S,T)=C(s,v2)+C(v1,t)= \sum _{v∈v_2^+}w_v + \sum _{v∈v_1^-}(-w_v) 因此C(S,T)=C(s,v2)+C(v1,t)=vv2+wv+vv1(wv)
    • w v 1 = ∑ v ∈ v 1 + w v − ∑ v ∈ v 1 − ( − w v ) w_{v_1}= \sum _{v∈v_1^+} w_v - \sum _{v∈v_1^-} (-w_v) wv1=vv1+wvvv1(wv)
    • 上诉两式相加得 C ( S , T ) + w v 1 = ∑ v ∈ v 2 + w v + ∑ v ∈ v 1 + w v = ∑ 为正数的点集 w v 上诉两式相加得 C(S,T)+w_{v_1}=\sum _{v∈v_2^+}w_v+\sum _{v∈v_1^+} w_v= \sum _{为正数的点集}w_v 上诉两式相加得C(S,T)+wv1=vv2+wv+vv1+wv=为正数的点集wv
    • 那么右边为定值,要让W_v最大即简单割最小
    • 而简单割的割边一定不可能在原图边上(因为容量无穷),因此最小割也为简单割
    • 因此求最小割,利用最终式子就能得到最大权子图了

P4174 [NOI2006] 最大获利

题意:
n个通信站,m个公司,每个公司有三个参数a b c,如果建立了a,b两个通信站,则获利c元,建立通信站花费pi
思路:

  • 图论里面用到的技巧,把边缩成一个点,这个点依赖于a,b两个点,那么把通信站看成负权点,边缩成的点为正权点,求最大权闭合子图
#include<bits/stdc++.h>
#define ll long long
#define x first
#define y second
const int mod=19930726;
const int INF=0x3f3f3f3f;
using namespace std;
typedef pair<int,int> PII;
const int N=55010,M=(N+5e4*2)*2;
int h[N],e[M],ne[M],f[M],idx;
int d[N],cur[N];
int n,m,S,T;
void add(int a,int b,int c){e[idx]=b,f[idx]=c,ne[idx]=h[a],h[a]=idx++;e[idx]=a,f[idx]=0,ne[idx]=h[b],h[b]=idx++;
}
bool bfs(){queue<int> q;memset(d,-1,sizeof d);q.push(S);d[S]=0,cur[S]=h[S];while(!q.empty()){auto u=q.front();q.pop();for(int i=h[u];~i;i=ne[i]){int v=e[i];if(d[v]==-1&&f[i]){d[v]=d[u]+1;cur[v]=h[v];if(v==T)return true;q.push(v);}}}return false;
}
int find(int u,int limit){if(u==T)return limit;int flow=0;for(int i=cur[u];~i&&flow<limit;i=ne[i]){cur[u]=i;int v=e[i];if(d[v]==d[u]+1&&f[i]){int t=find(v,min(f[i],limit-flow));if(!t)d[v]=-1;f[i]-=t,f[i^1]+=t,flow+=t;}}return flow;
}
int dinic(){int r=0,flow;while(bfs())while(flow=find(S,INF))r+=flow;return r;
}
void solve(){cin>>n>>m;S=0,T=n+m+1;int tot=0;memset(h,-1,sizeof h);for(int i=1;i<=n;i++){int p;cin>>p;add(m+i,T,p);}for(int i=1;i<=m;i++){int a,b,c;cin>>a>>b>>c;add(S,i,c);tot+=c;add(i,m+a,INF);add(i,m+b,INF);}cout<<tot-dinic()<<"\n";
}
signed main(){cin.tie(0)->sync_with_stdio(0);int T=1;//cin>>T;while(T--)solve();return 0;
}

最小割之最大密度子图

在G(V,E)中选择一个V’和E’,使得对于任意一条边(E’内)的u,v,都有u∈V' and v∈V'
最大化 ∣ E ′ ∣ ∣ V ′ ∣ 最大化\frac{|E'|}{|V'|} 最大化VE

  • 令 ∣ E ′ ∣ ∣ V ′ ∣ = g , 则按照 01 分数规划的思想,可以看出最大化 ∣ E ′ ∣ − g ∣ V ′ ∣ 令\frac{|E'|}{|V'|}=g,则按照01分数规划的思想,可以看出最大化|E'|-g|V'| VE=g,则按照01分数规划的思想,可以看出最大化EgV

模板题:UVA1389 Hard Life

最小割之最小权点覆盖

最小权点覆盖,在图中选最小的点,能覆盖所有边
二分图中,最大匹配数=最小点覆盖数=n(若有权值则表示总权值)-最大独立集数(权值大于1仍成立)

  • 建图,左边点向S连容量为点权的边,右边向T连容量为点权的边,左右两边建无穷边
  • 可证:点覆盖集<=>简单割
  • 因此求流网络最小割就好

POJ2125 Destroying The Graph

题意:
在这里插入图片描述

思路:
拆点,一个点拆成出去-和进来+
左边为+,右边为-就是一张二分图,求最小权点覆盖

  • 找割边
    • 从源点出发能到达的为S,其余为T
    • 枚举所有正向边,若两点为S->T即为割边

最小割之最大权独立集

独立集:选出的点集中不存在边

  • 任意的点覆盖集的补集都是独立集
    • 证明: 假设点覆盖集为 v 1 , v 2 = v − v 1 假设点覆盖集为v_1,v_2=v-v_1 假设点覆盖集为v1,v2=vv1
    • 假设 v 2 存在两个点之间存在边,则 v 1 不存在这两个点,则 v 1 不是点覆盖集,与假设矛盾 假设v_2存在两个点之间存在边,则v_1不存在这两个点,则v_1不是点覆盖集,与假设矛盾 假设v2存在两个点之间存在边,则v1不存在这两个点,则v1不是点覆盖集,与假设矛盾
    • 所以 v 2 不存在两个点有边, v 2 为独立集 所以v_2不存在两个点有边,v_2为独立集 所以v2不存在两个点有边,v2为独立集

P4474王者之剑

题意:
n*m的网格,每个网格有 v i , j v_{i,j} vi,j 个宝石,可以决定从哪里开始走

  • 第 i 秒开始时,如果有宝石,捡起 第i秒开始时,如果有宝石,捡起 i秒开始时,如果有宝石,捡起
  • 偶数秒周围四格宝石消失
  • 下一秒可以在上下左右或者原点位置

求最多拿宝石的数量
思路:

  • 分析可得只有偶数秒能取宝石
  • 不可能同时拿走相邻宝石(可以发现为一个独立集)(合法方案=>独立集)
  • 证明:独立集=>合法方案
    • 可以通过停顿一次来重置奇偶性,来拿到所有独立集的点
const int N=10010,M=60010;
int h[N],e[M],ne[M],f[M],idx;
int d[N],cur[N];
int n,m,S,T;
void add(int a,int b,int c){e[idx]=b,f[idx]=c,ne[idx]=h[a],h[a]=idx++;e[idx]=a,f[idx]=0,ne[idx]=h[b],h[b]=idx++;
}
bool bfs(){queue<int> q;memset(d,-1,sizeof d);q.push(S);d[S]=0,cur[S]=h[S];while(!q.empty()){auto u=q.front();q.pop();for(int i=h[u];~i;i=ne[i]){int v=e[i];if(d[v]==-1&&f[i]){d[v]=d[u]+1;cur[v]=h[v];if(v==T)return true;q.push(v);}}}return false;
}
int find(int u,int limit){if(u==T)return limit;int flow=0;for(int i=cur[u];~i&&flow<limit;i=ne[i]){cur[u]=i;int v=e[i];if(d[v]==d[u]+1&&f[i]){int t=find(v,min(f[i],limit-flow));if(!t)d[v]=-1;f[i]-=t,f[i^1]+=t,flow+=t;}}return flow;
}
int dinic(){int r=0,flow;while(bfs())while(flow=find(S,INF))r+=flow;return r;
}
int get(int x,int y){return (x-1)*m+y;
}
void solve(){cin>>n>>m;memset(h,-1,sizeof h);S=0,T=n*m+1;int dx[4]={-1,0,1,0},dy[4]={0,1,0,-1};int tot=0;for(int i=1;i<=n;i++){for(int j=1;j<=m;j++){int w;cin>>w;if(i+j&1){add(S,get(i,j),w);for(int k=0;k<4;k++){int x=i+dx[k],y=j+dy[k];if(x>=1&&x<=n&&y>=1&&y<=m){add(get(i,j),get(x,y),INF);}}}else add(get(i,j),T,w);tot+=w;}}cout<<tot-dinic()<<"\n";
}

SP300 CABLETV - Cable TV Network(最小割拆点)

题意:
给定一个n(n <= 50)个点的无向图,求它的点联通度。即最少删除多少个点,使得图不连通。
思路:

  • 图不连通最开始的情况肯定是分成两个集合,因此想到最小割
  • 题目是删点而不是删边,因此可以考虑拆点,一个点拆成入点和出点,容量为1;原图边容量为无穷(这样就不会是割边,就不会拆边)
  • 定义简单割为割边只在点内部,易证:简单割<=>极小点集
  • 枚举两个点当源点和汇点,跑最小割,所有最小割取min
const int N=110,M=52000;
int h[N],e[M],ne[M],f[M],idx;
int d[N],cur[N];
int n,m,S,T;
void add(int a,int b,int c){e[idx]=b,f[idx]=c,ne[idx]=h[a],h[a]=idx++;e[idx]=a,f[idx]=0,ne[idx]=h[b],h[b]=idx++;
}
bool bfs(){queue<int> q;memset(d,-1,sizeof d);q.push(S);d[S]=0,cur[S]=h[S];while(!q.empty()){auto u=q.front();q.pop();for(int i=h[u];~i;i=ne[i]){int v=e[i];if(d[v]==-1&&f[i]){d[v]=d[u]+1;cur[v]=h[v];if(v==T)return true;q.push(v);}}}return false;
}
int find(int u,int limit){if(u==T)return limit;int flow=0;for(int i=cur[u];~i&&flow<limit;i=ne[i]){cur[u]=i;int v=e[i];if(d[v]==d[u]+1&&f[i]){int t=find(v,min(f[i],limit-flow));if(!t)d[v]=-1;f[i]-=t,f[i^1]+=t,flow+=t;}}return flow;
}
int dinic(){int r=0,flow;while(bfs())while(flow=find(S,INF))r+=flow;return r;
}
int get(int x,int y){return (x-1)*m+y;
}
void solve(){while(cin>>n>>m){memset(h,-1,sizeof h);idx=0;for(int i=0;i<n;i++)add(i,n+i,1);for(int i=1;i<=m;i++){int u,v;scanf(" (%d,%d)",&u,&v);add(u+n,v,INF);add(v+n,u,INF);}int res=n;for(int i=0;i<n;i++){for(int j=0;j<i;j++){S=n+i,T=j;for(int k=0;k<idx;k+=2){f[k]+=f[k^1];f[k^1]=0;}res=min(res,dinic());}}cout<<res<<"\n";}
}

P2762 太空飞行计划问题(最大权闭合子图)

题意:
E={E1,E2,..}的实验,I={I1,I2,..}的仪器,实验可以获利,购买仪器需要钱,求最大净利润
思路:
裸的跑最大权闭合子图

  • 找方案:
    • S集合中所有的实验和仪器
    • 割边就是没有流量的边
const int N=110,M=5200;
int h[N],e[M],ne[M],f[M],idx;
int d[N],cur[N];
int n,m,S,T;
void add(int a,int b,int c){e[idx]=b,f[idx]=c,ne[idx]=h[a],h[a]=idx++;e[idx]=a,f[idx]=0,ne[idx]=h[b],h[b]=idx++;
}
bool bfs(){queue<int> q;memset(d,-1,sizeof d);q.push(S);d[S]=0,cur[S]=h[S];while(!q.empty()){auto u=q.front();q.pop();for(int i=h[u];~i;i=ne[i]){int v=e[i];if(d[v]==-1&&f[i]){d[v]=d[u]+1;cur[v]=h[v];if(v==T)return true;q.push(v);}}}return false;
}
int find(int u,int limit){if(u==T)return limit;int flow=0;for(int i=cur[u];~i&&flow<limit;i=ne[i]){cur[u]=i;int v=e[i];if(d[v]==d[u]+1&&f[i]){int t=find(v,min(f[i],limit-flow));if(!t)d[v]=-1;f[i]-=t,f[i^1]+=t,flow+=t;}}return flow;
}
int dinic(){int r=0,flow;while(bfs())while(flow=find(S,INF))r+=flow;return r;
}
bool st[N];
void dfs(int u){st[u]=true;for(int i=h[u];~i;i=ne[i]){if(!st[e[i]]&&f[i])dfs(e[i]);}
}
void solve(){cin>>m>>n;S=0,T=n+m+1;memset(h,-1,sizeof h);getchar();int tot=0;for(int i=1;i<=m;i++){int w,id;string line;getline(cin,line);stringstream ssin(line);ssin>>w;add(S,i,w);while(ssin>>id)add(i,m+id,INF);tot+=w;}for(int i=1;i<=n;i++){int p;cin>>p;add(m+i,T,p);}int res=dinic();dfs(S);for(int i=1;i<=m;i++){if(st[i])cout<<i<<" ";}cout<<"\n";for(int i=m+1;i<=m+n;i++){if(st[i])cout<<i-m<<" ";}cout<<"\n";cout<<tot-res<<"\n";
}

P3355 骑士共存问题(最大权独立集)

题意:
马(象棋走法)可以攻击八个方向的点,问在n*n中能放多少个马
思路:
i+j分奇偶,就是一个最大权独立集
最小割求即可

const int N=40010,M=N*10;
int h[N],e[M],ne[M],f[M],idx;
int d[N],cur[N];
int n,m,S,T;
void add(int a,int b,int c){e[idx]=b,f[idx]=c,ne[idx]=h[a],h[a]=idx++;e[idx]=a,f[idx]=0,ne[idx]=h[b],h[b]=idx++;
}
bool bfs(){queue<int> q;memset(d,-1,sizeof d);q.push(S);d[S]=0,cur[S]=h[S];while(!q.empty()){auto u=q.front();q.pop();for(int i=h[u];~i;i=ne[i]){int v=e[i];if(d[v]==-1&&f[i]){d[v]=d[u]+1;cur[v]=h[v];if(v==T)return true;q.push(v);}}}return false;
}
int find(int u,int limit){if(u==T)return limit;int flow=0;for(int i=cur[u];~i&&flow<limit;i=ne[i]){cur[u]=i;int v=e[i];if(d[v]==d[u]+1&&f[i]){int t=find(v,min(f[i],limit-flow));if(!t)d[v]=-1;f[i]-=t,f[i^1]+=t,flow+=t;}}return flow;
}
int dinic(){int r=0,flow;while(bfs())while(flow=find(S,INF))r+=flow;return r;
}
bool g[210][210];
int get(int x,int y){return (x-1)*n+y;
}
void solve(){cin>>n>>m;memset(h,-1,sizeof h);S=0,T=n*n+1;while(m--){int x,y;cin>>x>>y;g[x][y]=1;}int dx[]={-2,-1,1,2,2,1,-1,-2};int dy[]={1,2,2,1,-1,-2,-2,-1};int tot=0;for(int i=1;i<=n;i ++){for(int j=1;j<=n;j++){if(g[i][j])continue;if(i+j&1){add(S,get(i,j),1);for(int k=0;k<8;k++){int x=i+dx[k],y=j+dy[k];if(x>=1&&x<=n&&y>=1&&y<=n&&!g[x][y])add(get(i,j),get(x,y),INF);}}else add(get(i,j),T,1);tot++;}}cout<<tot-dinic()<<"\n";
}

费用流(所有最大流中的费用最大值/最小值)

总费用等于流量*当前的边的费用

最小费用最大流

顾名思义,最大流中费用最小的

  • 将EK算法的bfs换成spfa,沿着最短路增广的费用一定更小
  • 局限:无负权环
  • 费用:w(u,v)=-w(v,u)

EK模板

const int N=5010,M=100010;
int h[N],e[M],f[M],w[M],ne[M],idx;
int d[N],mxf[N],pre[N];//d表示最短距离,mxf表示最大流,pre表示到i的边的编号
int n,m,S,T;
bool vis[N];
void add(int a,int b,int c,int d){e[idx]=b,f[idx]=c,w[idx]=d,ne[idx]=h[a],h[a]=idx++;e[idx]=a,f[idx]=0,w[idx]=-d,ne[idx]=h[b],h[b]=idx++;
}
bool spfa(){queue<int> q;memset(d,0x3f,sizeof d);memset(mxf,0,sizeof mxf);q.push(S);d[S]=0,mxf[S]=INF;while(!q.empty()){int u=q.front();q.pop();vis[u]=0;for(int i=h[u];~i;i=ne[i]){int v=e[i];if(f[i]&&d[v]>d[u]+w[i]){d[v]=d[u]+w[i];pre[v]=i;mxf[v]=min(f[i],mxf[u]);if(!vis[v]){q.push(v);vis[v]=1;}}}}return mxf[T]>0;
}   
void EK(int &flow,int &cost){flow=cost=0;while(spfa()){int t=mxf[T];flow+=t,cost+=t*d[T];for(int i=T;i!=S;i=e[pre[i]^1]){f[pre[i]]-=t;f[pre[i]^1]+=t;}}
}
void solve(){cin>>n>>m>>S>>T;memset(h,-1,sizeof h);for(int i=1;i<=m;i++){int a,b,c,d;cin>>a>>b>>c>>d;add(a,b,c,d);}int flow,cost;EK(flow,cost);cout<<flow<<" "<<cost<<"\n";
}

多路增广费用流模板(效率与zkw相当)

  • EK还是太慢了,有些时候不好用
  • 但是dinic又是基于dijkstra,不能处理负权边
  • 那么有没有一种算法可以转换呢
  • 答案是有的,它就是Johnson 全源最短路径算法,经过转化可以处理负权边
  • 于是就可以dinic费用流了
int h[N],e[M],f[M],w[M],ne[M],idx;
int dis[N],cur[N];//d表示最短距离,cur为当前弧
int n,m,S,T;
bool vis[N];
int height[N];// 势能函数
vector<int> dot;// 点数
void add(int a,int b,int c,int d){e[idx]=b,f[idx]=c,w[idx]=d,ne[idx]=h[a],h[a]=idx++;e[idx]=a,f[idx]=0,w[idx]=-d,ne[idx]=h[b],h[b]=idx++;
}
bool dijk(){for(auto &i:dot)dis[i]=INF,cur[i]=h[i];priority_queue<PII,vector<PII>,greater<>> pq;dis[S]=0;pq.emplace(0,S);while(!pq.empty()){auto [d,u]=pq.top();pq.pop();if(dis[u]!=d)continue;for(int i=h[u];~i;i=ne[i]){int v=e[i];if(f[i]&&dis[v]>d+height[u]-height[v]+w[i]){dis[v]=d+height[u]-height[v]+w[i];pq.emplace(dis[v],v);}}}return dis[T]!=INF;
}   
int find(int u,int limit){if(!limit||u==T)return limit;int flow=0;vis[u]=1;for(int &i=cur[u];~i&&flow<limit;i=ne[i]){int v=e[i];if(dis[v]==dis[u]+height[u]-height[v]+w[i]&&f[i]&&!vis[v]){int t=find(v,min(f[i],limit-flow));f[i]-=t,f[i^1]+=t,flow+=t;if(flow==limit)break;}}vis[u]=0;return flow;
}
void mcmf(int &flow,int &cost){flow=cost=0;int r;while(dijk()){int r=find(S,INF);flow+=r;for(auto &i:dot)height[i]+=dis[i];cost+=height[T]*r;}
}

P4015 运输问题(最小+最大)

思路:

  • 运输方案<=>最大流
  • 最小花费最大流
  • 最大求法:
    • 将所有费用取反
    • 求最小费用,再取反
int h[N],e[M],f[M],w[M],ne[M],idx;
int d[N],mxf[N],pre[N];//d表示最短距离,mxf表示最大流,pre表示到i的边的编号
int n,m,S,T;
bool vis[N];
void add(int a,int b,int c,int d){e[idx]=b,f[idx]=c,w[idx]=d,ne[idx]=h[a],h[a]=idx++;e[idx]=a,f[idx]=0,w[idx]=-d,ne[idx]=h[b],h[b]=idx++;
}
bool spfa(){queue<int> q;memset(d,0x3f,sizeof d);memset(mxf,0,sizeof mxf);q.push(S);d[S]=0,mxf[S]=INF;while(!q.empty()){int u=q.front();q.pop();vis[u]=0;for(int i=h[u];~i;i=ne[i]){int v=e[i];if(f[i]&&d[v]>d[u]+w[i]){d[v]=d[u]+w[i];pre[v]=i;mxf[v]=min(f[i],mxf[u]);if(!vis[v]){q.push(v);vis[v]=1;}}}}return mxf[T]>0;
}   
void EK(int &flow,int &cost){flow=cost=0;while(spfa()){int t=mxf[T];flow+=t,cost+=t*d[T];for(int i=T;i!=S;i=e[pre[i]^1]){f[pre[i]]-=t;f[pre[i]^1]+=t;}}
}
void solve(){cin>>m>>n;S=0,T=n+m+1;memset(h,-1,sizeof h);for(int i=1;i<=m;i++){int x;cin>>x;add(S,i,x,0);}for(int i=1;i<=n;i++){int x;cin>>x;add(m+i,T,x,0);}for(int i=1;i<=m;i++){for(int j=1;j<=n;j++){int c;cin>>c;add(i,m+j,INF,c);}}int flow,cost;EK(flow,cost);cout<<cost<<"\n";for(int i=0;i<idx;i+=2){f[i]+=f[i^1],f[i^1]=0;w[i]=-w[i],w[i^1]=-w[i^1];}EK(flow,cost);cout<<-cost;
}

负载平衡问题

题意:
n个仓库组成一个环形,每个仓库可以向相邻点搬运货物,问所有仓库货物相等时,最少搬运量
思路:

  • 求出平均值x
  • 对于大于x的仓库,说明货需要出去,连到源点
  • 对于小于x的仓库,说明货要进来,连到汇点
  • 这样对于每个可行方案都会对应一个最大流
  • 则求最小费用,一条边的费用为1
const int N=110,M=1010;
int h[N],e[M],f[M],w[M],ne[M],idx;
int d[N],mxf[N],pre[N],s[N];//d表示最短距离,mxf表示最大流,pre表示到i的边的编号
int n,m,S,T;
bool vis[N];
void add(int a,int b,int c,int d){e[idx]=b,f[idx]=c,w[idx]=d,ne[idx]=h[a],h[a]=idx++;e[idx]=a,f[idx]=0,w[idx]=-d,ne[idx]=h[b],h[b]=idx++;
}
bool spfa(){queue<int> q;memset(d,0x3f,sizeof d);memset(mxf,0,sizeof mxf);q.push(S);d[S]=0,mxf[S]=INF;while(!q.empty()){int u=q.front();q.pop();vis[u]=0;for(int i=h[u];~i;i=ne[i]){int v=e[i];if(f[i]&&d[v]>d[u]+w[i]){d[v]=d[u]+w[i];pre[v]=i;mxf[v]=min(f[i],mxf[u]);if(!vis[v]){q.push(v);vis[v]=1;}}}}return mxf[T]>0;
}   
void EK(int &flow,int &cost){flow=cost=0;while(spfa()){int t=mxf[T];flow+=t,cost+=t*d[T];for(int i=T;i!=S;i=e[pre[i]^1]){f[pre[i]]-=t;f[pre[i]^1]+=t;}}
}
void solve(){cin>>n;S=0,T=n+1;int tot=0;memset(h,-1,sizeof h);for(int i=1;i<=n;i++){cin>>s[i],tot+=s[i];add(i,i>1?i-1:n,INF,1);add(i,i<n?i+1:1,INF,1);}tot/=n;for(int i=1;i<=n;i++){if(s[i]>tot)add(S,i,s[i]-tot,0);else add(i,T,tot-s[i],0);}int flow,cost;EK(flow,cost);cout<<cost<<"\n";
}

P4014 分配问题(二分图最优匹配)

题意:
n个工作分配给n个人,第i个人做第j个工作的费用为C_{i,j}求最大和最小花费
思路:
二分图,跑模板

const int N=110,M=1e5;
int h[N],e[M],f[M],w[M],ne[M],idx;
int d[N],mxf[N],pre[N];//d表示最短距离,mxf表示最大流,pre表示到i的边的编号
int n,m,S,T;
bool vis[N];
void add(int a,int b,int c,int d){e[idx]=b,f[idx]=c,w[idx]=d,ne[idx]=h[a],h[a]=idx++;e[idx]=a,f[idx]=0,w[idx]=-d,ne[idx]=h[b],h[b]=idx++;
}
bool spfa(){queue<int> q;memset(d,0x3f,sizeof d);memset(mxf,0,sizeof mxf);q.push(S);d[S]=0,mxf[S]=INF;while(!q.empty()){int u=q.front();q.pop();vis[u]=0;for(int i=h[u];~i;i=ne[i]){int v=e[i];if(f[i]&&d[v]>d[u]+w[i]){d[v]=d[u]+w[i];pre[v]=i;mxf[v]=min(f[i],mxf[u]);if(!vis[v]){q.push(v);vis[v]=1;}}}}return mxf[T]>0;
}   
void EK(int &flow,int &cost){flow=cost=0;while(spfa()){int t=mxf[T];flow+=t,cost+=t*d[T];for(int i=T;i!=S;i=e[pre[i]^1]){f[pre[i]]-=t;f[pre[i]^1]+=t;}}
}
void solve(){cin>>n;S=0,T=2*n+1;memset(h,-1,sizeof h);for(int i=1;i<=n;i++)add(S,i,1,0),add(n+i,T,1,0);for(int i=1;i<=n;i++){for(int j=1;j<=n;j++){int c;cin>>c;add(i,n+j,INF,c);}}int flow,cost;EK(flow,cost);cout<<cost<<"\n";for(int i=0;i<idx;i+=2){f[i]+=f[i^1];f[i^1]=0;w[i]=-w[i];w[i^1]=-w[i^1];}EK(flow,cost);cout<<-cost<<"\n";
}

P4013 数字梯形问题(拆点+费用流)

给定一个第一行为m个点,有n行的类似杨辉三角的梯形,从第一行每个点往下走m条路经,求最大总和
分别求满足以下三个规则的最大值:

  1. 从梯形的顶至底的 m 条路径互不相交;
  2. 从梯形的顶至底的 m 条路径仅在数字结点处相交;
  3. 从梯形的顶至底的 m 条路径允许在数字结点相交或边相交。
    思路:
  • 考虑规则1
    • 对于每个点只能走一次,可以考虑拆点,入点和出点连容量为1,费用为点数的边
    • 最上面点连源点,容量为1,费用为0
    • 最下面连汇点,容量1费用0
    • 按题意每个点连容量1费用0
  • 对于规则2,两条路径可以交叉在一个点
    • 即放开点内部的容量,连向其他的点容量仍为1,因此保证从当前点到下一个点不会被走两次
    • 往汇点的容量也放开,因为,多路径在最后一行汇集是合法的
  • 对于规则3,在2的基础上
    • 放开边的限制
const int N=1100,M=4000;
int h[N],e[M],f[M],w[M],ne[M],idx;
int d[N],mxf[N],pre[N];//d表示最短距离,mxf表示最大流,pre表示到i的边的编号
int n,m,S,T;
bool vis[N];
int id[40][40],C[40][40];
void add(int a,int b,int c,int d){e[idx]=b,f[idx]=c,w[idx]=d,ne[idx]=h[a],h[a]=idx++;e[idx]=a,f[idx]=0,w[idx]=-d,ne[idx]=h[b],h[b]=idx++;
}
bool spfa(){queue<int> q;memset(d,0x3f,sizeof d);memset(mxf,0,sizeof mxf);q.push(S);d[S]=0,mxf[S]=INF;while(!q.empty()){int u=q.front();q.pop();vis[u]=0;for(int i=h[u];~i;i=ne[i]){int v=e[i];if(f[i]&&d[v]>d[u]+w[i]){d[v]=d[u]+w[i];pre[v]=i;mxf[v]=min(f[i],mxf[u]);if(!vis[v]){q.push(v);vis[v]=1;}}}}return mxf[T]>0;
}   
void EK(int &flow,int &cost){flow=cost=0;while(spfa()){int t=mxf[T];flow+=t,cost+=t*d[T];for(int i=T;i!=S;i=e[pre[i]^1]){f[pre[i]]-=t;f[pre[i]^1]+=t;}}
}
void solve(){cin>>m>>n;int cnt=0;S=++cnt;T=++cnt;for(int i=1;i<=n;i++){for(int j=1;j<=m+i-1;j++){id[i][j]=++cnt;cin>>C[i][j];}}int flow,cost;//规则1memset(h,-1,sizeof h);idx=0;for(int i=1;i<=n;i++){for(int j=1;j<=m+i-1;j++){add(id[i][j]*2,id[i][j]*2+1,1,-C[i][j]);if(i==1)add(S,id[i][j]*2,1,0);if(i==n)add(id[i][j]*2+1,T,1,0);if(i<n){add(id[i][j]*2+1,id[i+1][j]*2,1,0);add(id[i][j]*2+1,id[i+1][j+1]*2,1,0);}}}EK(flow,cost);cout<<-cost<<"\n";//规则2memset(h,-1,sizeof h);idx=0;for(int i=1;i<=n;i++){for(int j=1;j<=m+i-1;j++){add(id[i][j]*2,id[i][j]*2+1,INF,-C[i][j]);if(i==1)add(S,id[i][j]*2,1,0);if(i==n)add(id[i][j]*2+1,T,1,0);if(i<n){add(id[i][j]*2+1,id[i+1][j]*2,1,0);add(id[i][j]*2+1,id[i+1][j+1]*2,1,0);}}}EK(flow,cost);cout<<-cost<<"\n"; //规则3memset(h,-1,sizeof h);idx=0;for(int i=1;i<=n;i++){for(int j=1;j<=m+i-1;j++){add(id[i][j]*2,id[i][j]*2+1,INF,-C[i][j]);if(i==1)add(S,id[i][j]*2,1,0);if(i==n)add(id[i][j]*2+1,T,INF,0);if(i<n){add(id[i][j]*2+1,id[i+1][j]*2,INF,0);add(id[i][j]*2+1,id[i+1][j+1]*2,INF,0);}}}EK(flow,cost);cout<<-cost<<"\n";
}

费用流之网格数问题

P2045 方格取数加强版(K取方格数)

题意:
(1,1)开始走,每次只能向右或向下走,走到(n,n),走k次,每个数只能取一次,求最大能取的值
思路:

  • 拆点,点权转边权,建容量为1的费用为点权的边
    • 因为下一次仍能走这个点,所以还要建容量为无穷费用为0的边
  • 然后源点与(1,1),(n,n)与汇点,每个能走到的点连边,容量无穷费用0
const int N=5010,M=20010;
int h[N],e[M],f[M],w[M],ne[M],idx;
int d[N],mxf[N],pre[N];//d表示最短距离,mxf表示最大流,pre表示到i的边的编号
int n,k,S,T;
bool vis[N];
void add(int a,int b,int c,int d){e[idx]=b,f[idx]=c,w[idx]=d,ne[idx]=h[a],h[a]=idx++;e[idx]=a,f[idx]=0,w[idx]=-d,ne[idx]=h[b],h[b]=idx++;
}
bool spfa(){queue<int> q;memset(d,0x3f,sizeof d);memset(mxf,0,sizeof mxf);q.push(S);d[S]=0,mxf[S]=INF;while(!q.empty()){int u=q.front();q.pop();vis[u]=0;for(int i=h[u];~i;i=ne[i]){int v=e[i];if(f[i]&&d[v]>d[u]+w[i]){d[v]=d[u]+w[i];pre[v]=i;mxf[v]=min(f[i],mxf[u]);if(!vis[v]){q.push(v);vis[v]=1;}}}}return mxf[T]>0;
}   
void EK(int &flow,int &cost){flow=cost=0;while(spfa()){int t=mxf[T];flow+=t,cost+=t*d[T];for(int i=T;i!=S;i=e[pre[i]^1]){f[pre[i]]-=t;f[pre[i]^1]+=t;}}
}
int get(int x,int y,int t){return (x-1)*n+y+t*n*n;
}
void solve(){cin>>n>>k;S=0,T=n*n*2+1;memset(h,-1,sizeof h);add(S,get(1,1,0),k,0);add(get(n,n,1),T,k,0);for(int i=1;i<=n;i++){for(int j=1;j<=n;j++){int c;cin>>c;add(get(i,j,0),get(i,j,1),1,-c);add(get(i,j,0),get(i,j,1),INF,0);if(i+1<=n)add(get(i,j,1),get(i+1,j,0),INF,0);if(j+1<=n)add(get(i,j,1),get(i,j+1,0),INF,0);}}int flow,cost;EK(flow,cost);cout<<-cost<<"\n";
}

P4012 深海机器人问题

与上题类似

P1251 餐巾计划问题

一个餐厅在相继的 N N N 天里,每天需用的餐巾数不尽相同。假设第 i i i 天需要 r i r_i ri 块餐巾( i = 1 , 2 , … , N i = 1, 2, \dots, N i=1,2,,N)。餐厅可以购买新的餐巾,每块餐巾的费用为 p p p 分;或者把旧餐巾送到快洗部,洗一块需 m m m 天,其费用为 f f f 分;或者送到慢洗部,洗一块需 n n n 天( n > m n \gt m n>m),其费用为 s s s 分( s < f s \lt f s<f)。

每天结束时,餐厅必须决定将多少块脏的餐巾送到快洗部,多少块餐巾送到慢洗部,以及多少块保存起来延期送洗。但是每天洗好的餐巾和购买的新餐巾数之和,要满足当天的需求量。

试设计一个算法为餐厅合理地安排好 N N N 天中餐巾使用计划,使总的花费最小。编程找出一个最佳餐巾使用计划。

思路:

  • 建图:
    • 左边为用过的毛巾(与源点连),右边为需要的毛巾(与汇点连)
    • 那么对于左边的毛巾
      • 要么什么都不干,即流向下一天用过的毛巾
      • 要么通过快洗机,流向n天之后的需求毛巾
      • 要么通过慢洗机,流向m天之后的需求毛巾
    • 对于右边毛巾,除了来自快洗和慢洗,还要与源点连边,表示购买的毛巾
    • 那么流网络满流时,右边需求一定恰好满(因为源点流出去无穷量,但是源点流向用过的毛巾不一定满,它可以流向下一天用过最终被丢掉)
const int N=4010,M=(N+2000*4)*2;
int h[N],e[M],f[M],w[M],ne[M],idx;
int d[N],mxf[N],pre[N];//d表示最短距离,mxf表示最大流,pre表示到i的边的编号
int n,m,S,T;
int p,x,xp,y,yp,r[N];
bool vis[N];
void add(int a,int b,int c,int d){e[idx]=b,f[idx]=c,w[idx]=d,ne[idx]=h[a],h[a]=idx++;e[idx]=a,f[idx]=0,w[idx]=-d,ne[idx]=h[b],h[b]=idx++;
}
bool spfa(){queue<int> q;memset(d,0x3f,sizeof d);memset(mxf,0,sizeof mxf);q.push(S);d[S]=0,mxf[S]=INF;while(!q.empty()){int u=q.front();q.pop();vis[u]=0;for(int i=h[u];~i;i=ne[i]){int v=e[i];if(f[i]&&d[v]>d[u]+w[i]){d[v]=d[u]+w[i];pre[v]=i;mxf[v]=min(f[i],mxf[u]);if(!vis[v]){q.push(v);vis[v]=1;}}}}return mxf[T]>0;
}   
void EK(int &flow,int &cost){flow=cost=0;while(spfa()){int t=mxf[T];flow+=t,cost+=t*d[T];for(int i=T;i!=S;i=e[pre[i]^1]){f[pre[i]]-=t;f[pre[i]^1]+=t;}}
}
void solve(){cin>>n;S=0,T=2*n+1;memset(h,-1,sizeof h);for(int i=1;i<=n;i++){cin>>r[i];}cin>>p>>x>>xp>>y>>yp;for(int i=1;i<=n;i++){add(S,i,r[i],0);add(n+i,T,r[i],0);add(S,n+i,INF,p);if(i+1<=n)add(i,i+1,INF,0);if(i+x<=n)add(i,n+i+x,INF,xp);if(i+y<=n)add(i,n+i+y,INF,yp);}int flow,cost;EK(flow,cost);cout<<cost<<"\n";
}

有源汇上下界费用流

  • 这里的费用流不需要满流,只需要符合条件就行

步骤:

  • 先把原图的边连了
  • 然后汇点 t t t s s s ( 0 , ∞ , 0 ) (0,\infty ,0) (0,,0)
  • 计算每个点的流入流出 F i F_i Fi
  • F i > 0 F_i \gt 0 Fi>0,附加源点 S S S i i i ( 0 , F i , 0 ) (0,F_i,0) (0,Fi,0)
  • 否则, i i i向附加汇点 T T T ( 0 , − F i , 0 ) (0,-F_i,0) (0,Fi,0)
  • 原图可行费用流=新图最大流费用流+ ∑ L o w i ⋅ w i \sum Low_i \cdot w_i Lowiwi(即该条边的下界*该条边的费用)
int h[N],e[M],f[M],w[M],ne[M],Low[M],idx;
int dis[N],cur[N];//d表示最短距离,cur为当前弧
int n,m,S,T;
bool vis[N];
int height[N];// 势能函数
vector<int> dot;// 点数
void add(int a,int b,int c,int d,int val){e[idx]=b,f[idx]=d-c,w[idx]=val,Low[idx]=c,ne[idx]=h[a],h[a]=idx++;e[idx]=a,f[idx]=0,w[idx]=-val,ne[idx]=h[b],h[b]=idx++;
}
bool dijk(){for(auto &i:dot)dis[i]=INF,cur[i]=h[i];priority_queue<PII,vector<PII>,greater<>> pq;dis[S]=0;pq.emplace(0,S);while(!pq.empty()){auto [d,u]=pq.top();pq.pop();if(dis[u]!=d)continue;for(int i=h[u];~i;i=ne[i]){int v=e[i];if(f[i]&&dis[v]>d+height[u]-height[v]+w[i]){dis[v]=d+height[u]-height[v]+w[i];pq.emplace(dis[v],v);}}}return dis[T]!=INF;
}   
int find(int u,int limit){if(!limit||u==T)return limit;int flow=0;vis[u]=1;for(int &i=cur[u];~i&&flow<limit;i=ne[i]){int v=e[i];if(dis[v]==dis[u]+height[u]-height[v]+w[i]&&f[i]&&!vis[v]){int t=find(v,min(f[i],limit-flow));f[i]-=t,f[i^1]+=t,flow+=t;if(flow==limit)break;}}vis[u]=0;return flow;
}
void mcmf(int &flow,int &cost){flow=cost=0;int r;for(auto &i:dot)height[i]=0;while(dijk()){int r=find(S,INF);flow+=r;for(auto &i:dot)height[i]+=dis[i];cost+=height[T]*r;}
}
void clear(){for(auto &i:dot)h[i]=-1;dot.clear();for(int i=0;i<idx;i++)e[i]=f[i]=w[i]=ne[i]=Low[i]=0;idx=0;
}
void solve() {clear();cin>>n>>m;int s=0,t=m+2*n+2;S=m+2*n+3,T=m+2*n+4;vector<int> F(m+2*n+3);// 出入for(int i=0;i<=m+2*n+4;i++)dot.push_back(i);for(int i=1;i<=m;i++){int l;cin>>l;add(s,i,l,INF,0);F[s]-=l,F[i]+=l;}vector<int> c(2*n+1),v(2*n+1);for(int i=1;i<=2*n;i++)cin>>c[i];for(int i=1;i<=2*n;i++)cin>>v[i];for(int i=1;i<=2*n;i++){add(c[i],i+m,0,1,-v[i]);add(i+m,i+m+1,(i+1)/2,INF,0);F[i+m]-=(i+1)/2,F[i+m+1]+=(i+1)/2;}add(m+2*n+1,t,n,n,0);F[m+2*n+1]-=n,F[t]+=n;int tot=0;for(int i=0;i<=m+2*n+2;i++){if(F[i]>0)add(S,i,0,F[i],0),tot+=F[i];else add(i,T,0,-F[i],0);}add(t,s,0,INF,0);int flow,cost;mcmf(flow,cost);// cout<<flow<<" "<<cost<<"\n";if(flow!=tot){cout<<"-1\n";return;}int res=-cost;for(int i=0;i<idx;i+=2){res+=Low[i]*w[i];}cout<<res<<"\n";
}

费用流杂题

P3980 [NOI2008] 志愿者招募

  • n n n天,第 i i i天需要 a i a_i ai人,有 m m m类人可以服务 s i s_i si t i t_i ti天,费用为 c c c,问最小费用
  • 如果这个人第二天不干了,那么可以直接二分图
  • 但是这个人可能会干到下一天,因此需要重新建模
  • 每天看作一条边, i i i i + 1 i+1 i+1连边,源点向 1 1 1连, n + 1 n+1 n+1向汇点连,流量为 I N F − a i INF-a_i INFai,代表不工作的人最多的流量
  • 每类人 s i s_i si t i + 1 t_i+1 ti+1连边,容量无穷,费用为 c i c_i ci,表示工作的人的通道
  • 最大流显然是 I N F INF INF
void solve(){memset(h,-1,sizeof h);int n,m;cin>>n>>m;S=0,T=n+2;for(int i=0;i<=n+2;i++)dot.push_back(i);add(S,1,INF,0);add(n+1,T,INF,0);for(int i=1;i<=n;i++){int x;cin>>x;add(i,i+1,INF-x,0);}for(int i=1;i<=m;i++){int s,t,c;cin>>s>>t>>c;add(s,t+1,INF,c);}int flow,cost;mcmf(flow,cost);cout<<cost<<"\n";
}

模拟费用流

模板:QOJ7185

题意:

一个二分图,左边 n ( ≤ 50000 ) n(\leq 50000) n(50000)个点,右边 m ( ≤ 10 ) m(\leq 10) m(10)个点,左边 i i i匹配右边 j j j的花费为 c i , j c_{i,j} ci,j,右边每个点的容量为 a i a_i ai,求最小花费

  • 很明显可以用费用流直接解
  • 但是我们要学的是模拟费用流,这是一个经典模型

我们把一条增广路拆成三部分:

  1. 源点到第一个右部点(源点->第一个左部点->第一个右部点)
  2. 右部点->左部点->右部点->左部点->…
  3. 最后一个右部点到汇点

我们考虑这样一条路径: i i i(右部点)到 x x x(左部点)到 j j j(右部点)

对应于网络流的定义即,原来的匹配 ( x , i ) (x,i) (x,i)取消,新的匹配 ( x , j ) (x,j) (x,j)生成

因此此时从 i i i j j j的最短路应该是 c x , j − c x , i c_{x,j}-c_{x,i} cx,jcx,i,可以使用小根堆维护 ( i , j ) (i,j) (i,j)的最短路

因此我们对于右部点就建立出新的图,可以跑spfa,求出右部点点到汇点的最短路

然后贪心选取费用最小的点即可

一共增广 n n n次,spfa时间为 m 3 m^3 m3,更新小根堆复杂度为 m 2 log ⁡ n m^2\log n m2logn

总时间复杂度 O ( n ( m 3 + m 2 log ⁡ n ) ) O(n(m^3+m^2\log n)) O(n(m3+m2logn))

void solve() {int n,m;// n<=50000,m<=10cin>>n>>m;vector c(n+1,vector<int>(m+1));vector<int> lim(n+1);for(int i=1;i<=n;i++){for(int j=1;j<=m;j++)cin>>c[i][j];// i->j的花费}for(int i=1;i<=m;i++)cin>>lim[i];// 容量vector s(m+1,vector<set<PII>>(m+1));// 路径i(右)->x(左)->j(右) ,维护a[x][j]-a[x][i]vector w(m+1,vector<int>(m+1));vector<int> be(n+1);// 左部点属于哪个右部点auto add=[&](int x,int id){be[x]=id;for(int i=1;i<=m;i++){if(i==id)continue;s[id][i].emplace(c[x][i]-c[x][id],x);}};auto del=[&](int x){int id=be[x];be[x]=-1;for(int i=1;i<=m;i++){if(i==id)continue;s[id][i].erase({c[x][i]-c[x][id],x});}};for(int i=1;i<=n;i++)add(i,0);queue<int> q;vector<int> dis(m+1),vis(m+1),pre(m+1);// dis为右部点到T的最小费用,vis为spfa辅助数组,pre记录前驱int ans=0;for(int t=1;t<=n;t++){// 枚举增广次数for(int i=0;i<=m;i++){pre[i]=-1;dis[i]=INF;for(int j=1;j<=m;j++){if(s[i][j].empty())w[i][j]=INF;else w[i][j]=s[i][j].begin()->first;}}q.push(0);dis[0]=0;vis[0]=1;pre[0]=0;while(!q.empty()){auto u=q.front();q.pop();vis[u]=0;for(int v=1;v<=m;v++){if(dis[v]>dis[u]+w[u][v]){dis[v]=dis[u]+w[u][v];pre[v]=u;if(!vis[v])q.push(v),vis[v]=1;}}}int id=-1;for(int i=1;i<=m;i++){if(lim[i]&&(id==-1||dis[i]<dis[id]))id=i;}if(id==-1)break;// 没有增广路了lim[id]--;ans+=dis[id];// 找到最小费用增广路径while(id){// 更新sint v=s[pre[id]][id].begin()->second;del(v);add(v,id);id=pre[id];}}cout<<ans<<"\n";
}

AGC018Coins

x + y + z x+y+z x+y+z个人,第 i i i个人有 A i A_i Ai个金币, B i B_i Bi个银币, C i C_i Ci个铜币

可以分别选择 x , y , z x,y,z x,y,z个人拿金银铜,求最多拿几个牌

那么,左边 n n n个点,右边 3 3 3个点,直接跑费用流会超时,因此模拟费用流即可(最大费用,边取反即可)

BZOJ4977 跳伞

Description
小Q最近沉迷于《跳伞求生》游戏。他组建了一支由n名玩家(包括他自己)组成的战队,编号依次为1到n。这个游
戏中,每局游戏开始时,所有玩家都会从飞机上跳伞,选择一个目的地降落,跳伞和降落的时间有早有晚。在某局
游戏降落前,他们在空中观察发现地面上一共有m间房子,编号依次为1到m。其中每间房子恰好有一名敌人早于他
们到达。小Q战队的第i名玩家拥有a_i发子弹,地面上第i间房子里的敌人拥有b_i发子弹,消灭他可以获得c_i点积
分。每名玩家必须且只能选择一间房子降落,然后去消灭里面的敌人。若第i名玩家选择了第j间房子,如果a_i>b_
j,那么他就可以消灭该敌人,获得a_i-b_j+c_j的团队奖励积分,否则他会被敌人消灭。为了防止团灭,小Q不允
许多名玩家选择同一间房子,因此如果某位玩家毫无利用价值,你可以选择让他退出游戏。因为房子之间的距离过
长,你可以认为每名玩家在降落之后不能再去消灭其它房间里的敌人。作为小Q战队的指挥,请制定一套最优的降
落方案,使得最后获得的团队奖励总积分最大

Input
第一行包含两个正整数n,m(1<=n,m<=100000),分别表示战队的玩家数和地面上的房间数。
第二行包含n个正整数a_1,a_2,…,a_n(1<=a_i<=100000),分别表示每个玩家的子弹数。
接下来m行,每行两个正整数b_i,c_i(1<=b_i,c_i<=100000),分别表示每个敌人的子弹数和奖励积分。

Output
输出一行一个整数,即最后获得的团队奖励总积分的最大值。

思路:

思路:对a,b排序,那么对于每个ai,他能匹配的都是一个b的前缀

建出费用流模型后,发现他本质上是个反悔贪心,对于当前的 a i a_i ai要么顶掉之前的a,要么和未匹配的b匹配

CF730I

一样的套路

2024百度之星决赛 旅行商

题目链接
也是一样的套路

XCPC网络流题目

2024ICPC第一场网络赛H

在这里插入图片描述

我们考虑合法括号序的trick:

  • 2 i − 1 2i-1 2i1必须有 i i i个左括号
  • 即对于第 i i i个位置而言,需要 ⌈ i 2 ⌉ \lceil \frac{i}{2} \rceil 2i个左括号

还有一个条件左括号的颜色数大于等于 l i l_i li

因为 n n n很小,考虑上下界费用流如何建图,定义一单位的流为左括号数量

分为两类点:颜色点,位置点

* ( 下界,上界,费用 ) (下界,上界,费用) (下界,上界,费用)

  • 源点 S S S向每种颜色 i i i建边 ( l i , ∞ , 0 ) (l_i,\infty,0) (li,,0)
  • 颜色 c i c_i ci向对应的下标 i i i建边 ( 0 , 1 , v i ) (0,1,v_i) (0,1,vi)
  • 下标 i i i i + 1 i+1 i+1建边 ( ⌈ i 2 ⌉ , ∞ , 0 ) (\lceil \frac{i}{2} \rceil,\infty,0) (⌈2i,,0)
  • 下标 2 n + 1 2n+1 2n+1向汇点 T T T建边 ( n , n , 0 ) (n,n,0) (n,n,0)

点数 n + m = 1000 n+m=1000 n+m=1000,边数 3000 3000 3000左右,上界 3 e 9 3e9 3e9左右

套板子即可,只跑了18ms

#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/tree_policy.hpp>
using namespace std;
using namespace __gnu_pbds;
#define int long long
#define F first
#define S second
#define TR tree<int,null_type,less<int>,rb_tree_tag,tree_order_statistics_node_update>
typedef pair<int,int> PII;
const int N=3e4+10,M=3e4+10;
const int INF=1e18;
const int mod=998244353;
// const int mod=1e9+7;
mt19937_64 rng(chrono::steady_clock::now().time_since_epoch().count());
int h[N],e[M],f[M],w[M],ne[M],Low[M],idx;
int dis[N],cur[N];//d表示最短距离,cur为当前弧
int n,m,S,T;
bool vis[N];
int height[N];// 势能函数
vector<int> dot;// 点数
void add(int a,int b,int c,int d,int val){e[idx]=b,f[idx]=d-c,w[idx]=val,Low[idx]=c,ne[idx]=h[a],h[a]=idx++;e[idx]=a,f[idx]=0,w[idx]=-val,ne[idx]=h[b],h[b]=idx++;
}
bool dijk(){for(auto &i:dot)dis[i]=INF,cur[i]=h[i];priority_queue<PII,vector<PII>,greater<>> pq;dis[S]=0;pq.emplace(0,S);while(!pq.empty()){auto [d,u]=pq.top();pq.pop();if(dis[u]!=d)continue;for(int i=h[u];~i;i=ne[i]){int v=e[i];if(f[i]&&dis[v]>d+height[u]-height[v]+w[i]){dis[v]=d+height[u]-height[v]+w[i];pq.emplace(dis[v],v);}}}return dis[T]!=INF;
}   
int find(int u,int limit){if(!limit||u==T)return limit;int flow=0;vis[u]=1;for(int &i=cur[u];~i&&flow<limit;i=ne[i]){int v=e[i];if(dis[v]==dis[u]+height[u]-height[v]+w[i]&&f[i]&&!vis[v]){int t=find(v,min(f[i],limit-flow));f[i]-=t,f[i^1]+=t,flow+=t;if(flow==limit)break;}}vis[u]=0;return flow;
}
void mcmf(int &flow,int &cost){flow=cost=0;int r;for(auto &i:dot)height[i]=0;while(dijk()){int r=find(S,INF);flow+=r;for(auto &i:dot)height[i]+=dis[i];cost+=height[T]*r;}
}
void clear(){for(auto &i:dot)h[i]=-1;dot.clear();for(int i=0;i<idx;i++)e[i]=f[i]=w[i]=ne[i]=Low[i]=0;idx=0;
}
void solve() {clear();cin>>n>>m;int s=0,t=m+2*n+2;S=m+2*n+3,T=m+2*n+4;vector<int> F(m+2*n+3);// 出入for(int i=0;i<=m+2*n+4;i++)dot.push_back(i);for(int i=1;i<=m;i++){int l;cin>>l;add(s,i,l,INF,0);F[s]-=l,F[i]+=l;}vector<int> c(2*n+1),v(2*n+1);for(int i=1;i<=2*n;i++)cin>>c[i];for(int i=1;i<=2*n;i++)cin>>v[i];for(int i=1;i<=2*n;i++){add(c[i],i+m,0,1,-v[i]);add(i+m,i+m+1,(i+1)/2,INF,0);F[i+m]-=(i+1)/2,F[i+m+1]+=(i+1)/2;}add(m+2*n+1,t,n,n,0);F[m+2*n+1]-=n,F[t]+=n;int tot=0;for(int i=0;i<=m+2*n+2;i++){if(F[i]>0)add(S,i,0,F[i],0),tot+=F[i];else add(i,T,0,-F[i],0);}add(t,s,0,INF,0);int flow,cost;mcmf(flow,cost);// cout<<flow<<" "<<cost<<"\n";if(flow!=tot){cout<<"-1\n";return;}int res=-cost;for(int i=0;i<idx;i+=2){res+=Low[i]*w[i];}cout<<res<<"\n";}
signed main(){cin.tie(0)->sync_with_stdio(0);int T=1;memset(h,-1,sizeof h);cin>>T;while(T--)solve();return 0;
}

相关文章:

  • C语言高频面试题目——内联函数和普通函数的区别
  • python番外
  • 部署Megatron - LM,快速上手使用
  • P3909 异或之积 解题报告
  • 使用 Nacos 的注意事项与最佳实践
  • 深入理解CSS中的`transform-origin`属性
  • 树莓派超全系列教程文档--(40)树莓派config.txt旧版GPIO控制、超频及条件过滤器
  • 2025.4.22学习日记 JavaScript的常用事件
  • 电力系统中为什么采用三相交流电?
  • 虚拟机的网络配置
  • Springboot整合MyBatisplus和快速入门
  • apt --fix-broken install报错? Nvidia-driver没办法安装x
  • 利用 SSH 实现 WordPress 网站的全面安全管理
  • 2023蓝帽杯初赛内存取证-6
  • synchronized锁
  • Unity设计模式实战:用单例和观察者模式优化你的游戏架构 (Day 36)
  • 【Dv3Admin】从零搭建Git项目安装·配置·初始化
  • 数据结构:栈
  • notepad++技巧:查找和替换:扩展 or 正则表达式
  • 《Android系统应用部署暗礁:OAT文件缺失引发的连锁崩溃与防御体系构建》
  • 马上评丨冒名顶替上中专,为何一瞒就是30多年?
  • 游客大理古城买瓜起争执:170克手机称出340克
  • 上海消保委调查二次元消费:手办与卡牌受欢迎,悦己和社交是动力
  • 耐克领跑女性运动市场:持续加码、创新,更多新增长点有望涌现
  • 北京理工大学:教师宫某涉嫌师德失范,暂停其一切职务活动
  • 四川省委统战部副部长(正厅级)张荣履新峨眉电影集团“一把手”