网络流
前言
由于CSP2021最后一题考了网络流,而本蒟蒻在暑假学了网络流后就没有碰过Dinic所以这几天就开始狂刷网络流的题。
网络流:是一种很奇怪但是又很好玩(玄学)的东西。
她可以用来解决一些多限制的问题,比如说最小费用最大流就在很多方面都有用处。
网络流的板子有点难写(没有一次是一边写对的)。
但是,当把板子背下来后,看着题就把板子贴上去就可以了。
所以,网络流难在建立模型,每一道题的模型都千奇百怪,但又有着内在的联系,这就是我要总结的原因
1.最大流问题
首先是最大流的板子:(代码丑,多多见谅)
#include<bits/stdc++.h>
#define int long long
#define in read()
const int N=1210;
const int M=120005;
const int INF=0x7f3f3f3f;
int n,m;
int src,des,lev[N],cur[N];
int ecnt,head[N],nxt[M],to[M],cap[M];
inline void addEdge(const int &u,const int &v,const int &w){
nxt[++ecnt]=head[u],head[u]=ecnt,to[ecnt]=v,cap[ecnt]=w;
nxt[++ecnt]=head[v],head[v]=ecnt,to[ecnt]=u,cap[ecnt]=0;
}
inline bool Bfs(){
std::queue<int>q;
int u,v,e;
for(int i=1;i<=n;i++){
lev[i]=-1;
cur[i]=head[i];
}
q.push(src);
lev[src]=0;
while(!q.empty()){
u=q.front();
q.pop();
for(e=head[u];e;e=nxt[e]){
if(cap[e]>0&&lev[v=to[e]]==-1){
lev[v]=lev[u]+1;
q.push(v);
if(v==des) return 1;
}
}
}
return 0;
}
inline int Dinic(int u,int flow){
if(u==des) return flow;
int res=0,v,delta;
for(int &e=cur[u];e;e=nxt[e]){
if(cap[e]>0&&lev[u]<lev[v=to[e]]){
delta=Dinic(v,std::min(cap[e],flow-res));
if(delta){
cap[e]-=delta;
cap[e^1]+=delta;
res+=delta;
if(res==flow) break;
}
}
}
if(res!=flow) lev[u]=-1;
return res;
}
inline int maxFlow(){
int ans=0;
while(Bfs()) ans+=Dinic(src,INF);
return ans;
}
inline int read(){
static char ch;
int res=0,sign=1;
while((ch=getchar())<'0'||ch>'9'){
if(ch=='-'){
sign=-1;
}
}
res=ch-'0';
while((ch=getchar())>='0'&&ch<='9'){
res=res*10+ch-'0';
}
return res*sign;
}
inline void print(int x){
if(x<0){
x=~x+1;
putchar('-');
}
if(x>9) print(x/10);
putchar(x%10+'0');
}
signed main()
{
n=in,m=in,src=in,des=in;
ecnt=1;
for(int i=1;i<=m;i++){
int u,v,w;
u=in,v=in,w=in;
addEdge(u,v,w);
}
print(maxFlow());
return 0;
}
最大流在二分图匹配上也有用处只不过跑的比匈牙利慢一点。
放一道
飞行员配对问题
标准的二分图匹配:
用最大流,先建立一个源点在建立一个汇点,在源点与左部点之间建一条流量为一的边,在汇点和右部点之间建边。再在中间建边,跑最大流。
#include<bits/stdc++.h>
#define in read()
using namespace std;
const int N=5e4;
int n,m,k;
int src,des;
int head[N],to[N*2],nxt[N*2],cap[N*2];
int cur[N],lev[N];
queue<int> q;
int cnt=1;
void add(int u,int v,int z){
nxt[++cnt]=head[u],head[u]=cnt,to[cnt]=v,cap[cnt]=1;
nxt[++cnt]=head[v],head[v]=cnt,to[cnt]=u,cap[cnt]=0;
}
inline bool bfs(){
for(int i=0;i<=n+m+1;i++){
cur[i]=head[i];
lev[i]=-1;
}
q.push(src);
lev[src]=0;
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=head[u];i;i=nxt[i]){
int v=to[i];
if(cap[i]>0&&lev[v]==-1){
lev[v]=lev[u]+1;
q.push(v);
}
}
}
if(lev[des]!=-1){
return 1;
}else{
return 0;
}
}
inline int dfs(int u,int flow){
if(u==des){
return flow;
}
int res=0,delta;
for(int i=cur[u];i;i=nxt[i]){
cur[u]=1;
int v=to[i];
if(cap[i]>0&&lev[v]==lev[u]+1){
delta=dfs(v,min(flow-res,cap[i]));
if(delta){
cap[i]-=delta;
cap[i^1]+=delta;
res+=delta;
if(res==flow){
break;
}
}
}
}
return res;
}
inline int dinic(){
int ans=0;
while(bfs()){
ans+=dfs(src,0x3f3f3f3f);
}
return ans;
}
int cut[1001][1001];
inline int read(){
static char ch;
int res=0,sign=1;
while((ch=getchar())<'0'||ch>'9'){
if(ch=='-'){
sign=-1;
}
}
res=ch-'0';
while((ch=getchar())>='0'&&ch<='9'){
res=res*10+ch-'0';
}
return res*sign;
}
inline void print(int x){
if(x<0){
x=~x+1;
putchar('-');
}
if(x>9) print(x/10);
putchar(x%10+'0');
}
int main()
{
n=in,m=in,k=in;
src=0;
des=n+m+1;
for(int i=1;i<=n;i++){
add(src,i,1);
}
for(int i=n+1;i<=n+m;i++){
add(i,des,1);
}
for(int i=1;i<=k;i++){
int x,y;
x=in,y=in;
if(!cut[x][y+n]){
add(x,y+n,1);
cut[x][y+n]=1;
}
}
print(dinic());
}
2.最小割
根据一堆大佬的证明,我们可以清晰的知道最大流=最小割(别问我,我也不知道)。
这样就可以做水题了。
—————分割线——————
是这样的,在打完上面的话后,我去找了一道最小割的题来做。是道蓝题,我做了两个小时,看了题解才过。
就是它:最小割点
一开始看了,打了个Dinic模板结果只有80分,在看题返现是割点不是割边。
看了大佬的题解才懂。
要一个节点建立两个点,一个是入点,一个是出点。两个内部点之间连一条1的边,其他边也都是1.
再跑Dinic;
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5;
const int inf=0x3f3f3f3f;
int n,m,src,des;
int head[N],to[N*4],nxt[N*4],cap[N*4];
int cnt=1;
void add(int u,int v,int p){
nxt[++cnt]=head[u],head[u]=cnt,to[cnt]=v,cap[cnt]=p;
nxt[++cnt]=head[v],head[v]=cnt,to[cnt]=u,cap[cnt]=0;
}
int dis[N],cur[N];
queue<int> q;
inline bool bfs(){
for(int i=1;i<=4*n;i++){
cur[i]=head[i];
dis[i]=-1;
}
q.push(src);
dis[src]=0;
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=head[u];i;i=nxt[i]){
int v=to[i];
if(cap[i]>0&&dis[v]==-1){
dis[v]=dis[u]+1;
q.push(v);
}
}
}
return dis[des]!=-1;
}
int ff[N],in[N];
inline int dfs(int u,int flow){
in[N]+=flow;
if(u==des){
return flow;
}
int res=0,delta;
for(int i=cur[u];i;i=nxt[i]){
cur[u]=1;
int v=to[i];
if(cap[i]&&dis[v]==dis[u]+1){
delta=dfs(v,min(flow-res,cap[i]));
if(delta){
cap[i]-=delta;
cap[i^1]+=delta;
res+=delta;
ff[u]+=delta;
if(res==flow){
return 1;
}
}
}
}
if(res!=flow){
dis[u]=-1;
}
return res;
}
inline int read(){
static char ch;
int res=0,sign=1;
while((ch=getchar())<'0'||ch>'9'){
if(ch=='-'){
sign=-1;
}
}
res=ch-'0';
while((ch=getchar())>='0'&&ch<='9'){
res=res*10+ch-'0';
}
return res*sign;
}
inline void print(int x){
if(x<0){
x=~x+1;
putchar('-');
}
if(x>9) print(x/10);
putchar(x%10+'0');
}
inline int dinic(){
int ans=0;
while(bfs()){
ans+=dfs(src,inf);
}
return ans;
}
signed main()
{
n=read(),m=read();
src=read(),des=read();
int x,y;
for(int i=1;i<=n;i++){
if(i==src){
add(i,i+n,inf);
}else{
add(i,i+n,1);
}
}
for(int i=1;i<=m;i++){
x=read(),y=read();
if(x==src&&y==des||x==des&&y==src){
cout<<1<<endl;
}
add(x+n,y,1);
add(y+n,x,1);
}
cout<<dinic()<<endl;
return 0;
}
然后才过了……
3.费用流
在最大流的基础上,加上费用限制。
一道好玩的题:餐巾计划
传送门
建模很妙:
#include<bits/stdc++.h>
#define N 2*n+1
#define inf 2147483647
#define in read()
using namespace std;
#define int long long
struct node{
int u,nxt,w,f;
}e[300000];
long long n,p,b,f,a,s,st=1,cost,ans;
int day[100000],dis[100000],fir[100000],cur[100000];
queue<int>q;
bool v[100000];
bool spfa()
{
for(int i=0;i<=N;i++){
dis[i]=inf/2;
cur[i]=fir[i];
}
q.push(0);
dis[0]=0;
while(!q.empty()){
int k=q.front();
q.pop();
for(int i=fir[k];i;i=e[i].nxt){
int u=e[i].u;
int w=e[i].f;
if(dis[u]>dis[k]+w&&e[i].w){
dis[u]=dis[k]+w;
if(!v[u]) q.push(u);
}
}
}
return (dis[N]<inf/2);
}
int dfs(int p,int flow){
if(p==N){
v[N]=1;
ans+=flow;
return flow;
}
int delta=0,used=0;
for(int i=cur[p];i;i=e[i].nxt){
cur[p]=1;
int u=e[i].u,w=e[i].f;
if(dis[u]==dis[p]+w&&e[i].w){
if(delta=dfs(u,min(flow-used,e[i].w))){
e[i].w-=delta;
e[i^1].w+=delta;
used+=delta;
cost+=w*delta;
if(used==flow){
break;
}
}
}
}
return used;
}
long long dinic()
{
while(spfa()){
dfs(0,inf);
}
return cost;
}
void add(int x,int y,int w,int f){
e[++st].u=y,e[st].nxt=fir[x];e[fir[x]=st].w=w,e[st].f=f;
e[++st].u=x,e[st].nxt=fir[y],e[fir[y]=st].w=0,e[st].f=-f;
}
inline long long read(){
static char ch;
long long res=0,sign=1;
while((ch=getchar())<'0'||ch>'9'){
if(ch=='-'){
sign=-1;
}
}
res=ch-'0';
while((ch=getchar())>='0'&&ch<='9'){
res=res*10+ch-'0';
}
return res*sign;
}
inline void print(int x){
if(x<0){
x=~x+1;
putchar('-');
}
if(x>9) print(x/10);
putchar(x%10+'0');
}
signed main()
{
n=in;
for(int i=1;i<=n;i++){
day[i]=in;
add(0,i,day[i],0);
add(i+n,N,day[i],0);
}
p=in,a=in,f=in,b=in,s=in;
for(int i=1;i<=n;i++)
{
add(0,i+n,inf,p);
if(i+1<=n) add(i,i+1,inf,0);
if(i+a<=n) add(i,i+n+a,inf,f);
if(i+b<=n) add(i,i+n+b,inf,s);
}
print(dinic());
return 0;
}
是不是很妙呀!!
又一道水题:传送门
把多的点和少的点分开和源点还有汇点建边。
然后再建环边。
#include<bits/stdc++.h>
#define inf 2147483647
using namespace std;
int n;
struct node{
int flow,price,nxt,to;
}e[300001];
int head[10001],dis[10001],cur[10001],cnt=1;
int cost;
int src,des;
void add(int x,int y,int f,int c){
e[++cnt].nxt=head[x],head[x]=cnt,e[cnt].to=y,e[cnt].price=c,e[cnt].flow=f;
e[++cnt].nxt=head[y],head[y]=cnt,e[cnt].to=x,e[cnt].price=-c,e[cnt].flow=0;
}
queue<int> q;
inline bool spfa()
{
for(int i=0;i<=n+3;i++){
dis[i]=inf/2;
cur[i]=head[i];
}
q.push(src);
dis[src]=0;
while(!q.empty())
{
int u=q.front();
q.pop();
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(dis[v]>dis[u]+e[i].price&&e[i].flow){
dis[v]=dis[u]+e[i].price;
q.push(v);
}
}
}
return (dis[des]<inf/2);
}
//inline int dfs(int now,int flow){
// if(now==des){
// cost+=dis[des]*flow;
// return flow;
// }
// int res=0,delta;
// for(int i=cur[now];i;i=e[i].nxt){
// cur[now]=1;
// int v=e[i].to;
// if(e[i].flow&&dis[v]==dis[now]+e[i].price){
// delta=dfs(v,min(flow-res,e[i].flow));
// if(delta){
// e[i].flow-=delta;
// e[i^1].flow+=delta;
// res+=delta;
// if(res==flow){
// break;
// }
// }
// }
// }
// return res;
//}
inline int dfs(int now,int flow){
if(now==des){
cost+=dis[des]*flow;
return flow;
}
int res=0,delta=0;
for(int i=cur[now];i;i=e[i].nxt){
cur[now]=1;
int v=e[i].to;
if(dis[v]==dis[now]+e[i].price&&e[i].flow){
delta=dfs(v,min(flow-res,e[i].flow));
if(delta){
e[i].flow-=delta;
e[i^1].flow+=delta;
res+=delta;
if(res==flow){
break;
}
}
}
}
return res;
}
inline int dinic()
{
while(spfa()){
dfs(src,inf);
}
return cost;
}
int a[10001],sum;
inline int read(){
static char ch;
int res=0,sign=1;
while((ch=getchar())<'0'||ch>'9'){
if(ch=='-'){
sign=-1;
}
}
res=ch-'0';
while((ch=getchar())>='0'&&ch<='9'){
res=res*10+ch-'0';
}
return res*sign;
}
inline void print(int x){
if(x<0){
x=~x+1;
putchar('-');
}
if(x>9) print(x/10);
putchar(x%10+'0');
}
int main(){
n=read();
for(int i=1;i<=n;++i){
a[i]=read();
sum+=a[i];
}
sum/=n;src=0;des=n+1;
for(int i=1;i<=n;++i) a[i]-=sum;
for(int i=1;i<=n;++i){
if(a[i]>0)
add(src,i,a[i],0);
else if(a[i]<0)
add(i,des,-a[i],0);
}
for(int i=1;i<=n;++i){
if(i!=1)
add(i,(i-1),inf,1);
if(i!=n)
add(i,(i+1),inf,1);
}
add(1,n,inf,1);
add(n,1,inf,1);
dinic();
print(cost);
return 0;
}
还是一道水题:
更水了。
感觉越来越水了~~~~
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效