网络流24题小结

Posted 66t6

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了网络流24题小结相关的知识,希望对你有一定的参考价值。

网络流24题

前言

网络流的实战应用篇太难做了,因此先完善这一部分

## 第一题:飞行员配对方案

(BSOJ2542)——二分图 最优匹配

题意

两国飞行员(x)集合(y)集合,(x)飞行员可以配对特定的(y)集合的飞行员(可无),求一对一配对最大数

Solution

二分图最大匹配裸题,最大流实现

建图:(设(iin x)(i'in y))

((S,i,1)~(i',T,1))

((i,j'))可匹配((i,j',1))

Code

## 第二题:太空飞行计划

(BSOJ2543)——最小割 最大权闭合图+输出方案

题意

(m)个实验,每个实验只可以进行一次,但会获得相应的奖金,有(n)个仪器,每个实验都需要一定的仪器,每个仪器可以运用于多个实验,但需要一定的价值,问奖金与代价的差的最大值是多少?

Solution

我们会发现每个实验都需要一定的仪器相当于实验对仪器有选择的强制约束

这会让我们想到闭合图,权值最大就是最大权闭合图

建图:
(i)表示实验,(i')表示仪器
((S,i,a_i)~(i,j',INF)~(i',T,b_{i'}))
求最大权闭合图(正点权-最小割)即可

Code

## 第三题:最小路径覆盖

(BSOJ2569)——DAG 最小覆盖 最大匹配

题意

求出并输出无向图的一个最小点不相交路径覆盖

Solution

首先结论:最小点不相交路径覆盖=点数-最小割

涉及点经过次数拆点入点出点

建图:

((S,i,INF),(i,i',1),(i,j',1),(i',T,INF))

然后输出方案就模仿倒着满流边走,增广中间改即可

Code

dfs改成

inline int dfs(re int x,re int maxflow){
    re int dlt,y,res=0;
    if(x==T)return maxflow;
    for(re int&i=cur[x];i;i=e[i].next){
        y=e[i].to;
        if(dis[x]==dis[y]+1&&e[i].flow){
            dlt=dfs(y,min(maxflow,e[i].flow));
            e[i].flow-=dlt;e[i^1].flow+=dlt;
            res+=dlt;maxflow-=dlt;
            if(dlt>0&&x^S&&y^T&&x<=n&&y>n)a[x].to=y-n,a[y-n].from=x;
            if(!maxflow||dis[S]==P)return cur[x]=h[x],res; 
        }
    } 
    if(!(--gap[dis[x]]))dis[S]=P;++gap[++dis[x]];
    return cur[x]=h[x],res;
}

Print就

inline void Print(re int x){
    if(!x)return ;
    if(a[x].from^x)Print(a[x].from);
    vis[x]=1;printf("%d ",x); 
}

## 第四题:魔术球问题

(BSOJ2545)——最小割 最小路径覆盖

题意

满足条件的球可以放在一根柱子上,求最多球再加柱子不够

Solution

把合法关系网当成一个无向图
则问题转化为上一题(BSOJ2569)——DAG 最小覆盖 最大匹配
枚举个数向内加点加边动态求最小割即可

Code

## 第五题:圆桌问题

(BSOJ2546)——二分图多重匹配

题意

(n)个公司,(m)个桌子有各自最大座位(w_i),每个公司的人不能坐一个桌子,求可行否

Solution

不完备的二分图多重匹配

建图:
((i)表公司,(j)表桌子)
((S,i,a_i))限定来的人数(最大流跑满上界)
((i,j',1))一个桌一个同公司的人
((i',T,b_i))限定坐得人上界
判定满流即可

Code

## 第六题:最长递增子序列

(BSOJ2547)——最大流 dp建图

题意

求一个序列

  • 最长递增子序列的长度(maxl)
  • 可取出的长度为(maxl)的递增子序列个数。
  • 允许在取出的序列中多次使(a_1)(a_n)时可取出的长度为(maxl)的递增子序列个数。

Solution

首先(O(n^2)dp)解决第一问,记住不要用(O(nlog_2 n))的因为我们需要保存每个位置结束的最长递增子序列

然后是dp建图的经典套路

先拆点(i),(i')

对第二问,建图如下:

首先每个点((i,i',1))

(dp_i=1)表示这个点可以作为开始((S,i,1))

(dp_i=maxl)表示这个点可以作为结束((i',T,1))

其余时刻((i,j))可以中转((i)可以跟(j)后)当且仅当(dp_i+1=dp_j且a_i<a_j且i<j)((i',j,1))

对第三问,额外考虑(1,n)可以作为起/终点的时候边权改为(INF)即可

Code

for(i=1;i<=n;++i){
    AddEdge(i,i+n,1);
    if(dp[i]==1)AddEdge(S,i,1);
    if(dp[i]==ansl)AddEdge(i+n,T,1);
}
for(i=2;i<=n;++i)
    for(j=1;j<i;++j)if(a[i]>a[j]&&dp[i]==dp[j]+1)AddEdge(j+n,i,1);
printf("%d
",ISAP());
cnt=1,memset(h,0,P<<2);
for(i=1;i<=n;++i){
    AddEdge(i,i+n,1);
    if(dp[i]==1)AddEdge(S,i,1);
    if(dp[i]==ansl)AddEdge(i+n,T,1);
}
for(i=2;i<=n;++i)
    for(j=1;j<i;++j)if(a[i]>a[j]&&dp[i]==dp[j]+1)AddEdge(j+n,i,1);
AddEdge(S,1,INF);AddEdge(1,1+n,INF);AddEdge(n,n<<1,INF);if(dp[n]==ansl)AddEdge(n<<1,T,INF);
printf("%d
",ISAP());
//by zzw4257 禁止侵权

## 第七题:试题库问题

(BSOJ2548)——最大流 dp建图

题意

(n)道试题,每道试题都标明了所属类别.现要从题库中抽取(m)道题组成试卷.并要求试卷包含指定类型的试题.问可行性

Solution

几乎同第五题

Code

## 第八题:机器人路径规划问题

(BSOJ2549)——不可做题

## 第九题:方格取数问题

(BSOJ2550)——黑白染色 二分图最大匹配

题意

从方格中取数,使任意(2)个数所在方格没有公共边,且取出的数的总和最大

Solution

使任意(2)个数所在方格没有公共边这句话让我们想起黑白染色,问题转化为黑白集合的最大独立集

最大独立集=点权和-最小独立集(最小割)

Code

## 第十题:餐巾问题

(BSOJ2445)——线性规划,费用流

题意

过长

Solution

考虑全意义

建图:

((S,i',r_i,p))表示在这一天买新餐巾
((i',T,r_i,0))表示这一天用了(r_i)的餐巾
((S,i,r_i,0))表示这一天有(r_i)条脏餐巾
(i<n~(i,i+1,INF,0))这一天的脏餐巾剩到第二天
(i+m<=n~(i,(i+f)',INF,f))送去快洗,INF是因为这一天的脏餐巾不止这一天剩下的,还有之前剩下的
(i+t<=n~(i,(i+t)',INF,s))送去慢洗,INF是因为这一天的脏餐巾不止这一天剩下的,还有之前剩下的

Code

inline void Read(void){
    re int i,x;
    n=read(),p=read(),m=read(),f=read(),t=read(),s=read(),S=0,T=n<<1|1,P=T+1;
    for(i=1;i<=n;++i){
        x=read();
        AddEdge(S,i,x,0);
        AddEdge(S,i+n,x,p);
        AddEdge(i+n,T,x,0);
        if(i+m<=n)AddEdge(i,i+m+n,INF,f);
        if(i+t<=n)AddEdge(i,i+t+n,INF,s);
        if(i^n)AddEdge(i,i+1,INF,0);
    }
}

## 第十一题:航空路线问题

(BSOJ2551)——最长不相交路径,最小费用流

题意

给定一张航空图,图中顶点代表城市,边代表两城市间的直通航线。现要求找出一条满足下述限制条件的且途经城市最多的旅行路线。
(1)从最西端城市出发,单向从西向东途经若干城市到达最东端城市,然后再单向从东向西飞回起点(可途经若干城市)。
(2)除起点城市外,任何城市只能访问(1)次。

Solution

从西到东再到西,实际上就是从西向东飞两次。

建图:

先拆点普通点跑一次((i,i',1,1)~(iin(1,n)))

对最东最西城市可以走两次((1,1',2,1),(n,n',2,1))

((x,y))((x',y,1,0))

注意特判横跨(1,n)的边

Code

inline void Read(void){
    re int i,x,y;re string s1,s2;n=read(),m=read();S=1,T=n<<1,P=T+1;
    for(i=1;i<=n;++i)cin>>s1,mp[s1]=i;
    for(i=1;i<=m;++i){
        cin>>s1>>s2;x=mp[s1],y=mp[s2];
        if(x>y)swap(x,y);
        if(x==1&&y==n)flag=1;
        AddEdge(x+n,y,1,0);
    }
    for(i=2;i<n;++i)AddEdge(i,i+n,1,1);
    AddEdge(1,n+1,2,1),AddEdge(n,n<<1,2,1);
}
inline void Solve(void){Dinic();if(flag&&ansf==1)puts("2");else if(ansf==2)printf("%d
",ansc-2);else puts("No Solution!");}

## 第十二题:软件补丁

(BSOJ2552)——状压,最短路

题意

有一些补丁在包含(b1)不包含(b2)时可以使用,可以去掉(f1),增加(f2)

Solution

用病毒集合来做状态,状态之间跑病毒覆盖的最短路

Code

inline void SPFA(void){
    re int i,x,y;re std::queue<int> q;
    for(i=T;i<=S;++i)dis[i]=INF;
    q.push(S);vis[S]=1;dis[S]=0;
    while(!q.empty()){
        x=q.front(),q.pop(),vis[x]=0;
        for(i=1;i<=m;++i)if(Check(x,i)){
            y=(x&(~a[i].f1))|a[i].f2;
            if(dis[x]+a[i].v<dis[y]){dis[y]=dis[x]+a[i].v;if(vis[y])continue;q.push(y);}
        }
    }
    if(dis[T]>=INF)puts("0");else printf("%d
",dis[T]);
}

## 第十三题:星际转移问题

(BSOJ2553)——并查集 最大流

题意

(n)个飞船,每个飞船有循环行驶路线,有限载人数,求所有人从月到地最短时间

Solution

首先用并查集导出连通性判无解

然后枚举答案(时间)

以时间拆点

建图:
((S,0,k))送走地球上(k)个人
((n+1,T,INF))
((i^k,i^{k+1},INF))上标表示时间,表示可以待在空间站里
((i^k,j^{k+1},s_i(载重)))表示前一天某一空间站的几个人民可以通过一个飞船坐到后一天的另一个空间站

Code

inline void Solve(void){
    re int i,j,p1,p2;
    if(getf(0)^getf(n+1))return puts("0"),void();
    for(ans=1;;++ans){
        for(i=0;i<=n;++i)AddEdge((ans-1)*(n+2)+i,ans*(n+2)+i,INF);
        AddEdge(ans*(n+2)+n+1,T,INF);
        for(i=1;i<=m;++i){
            p1=ans%*sta[i];if(!p1)p1=*sta[i];
            p2=(p1+1)%*sta[i];if(!p2)p2=*sta[i];
            p1=sta[i][p1],p2=sta[i][p2];
            AddEdge((ans-1)*(n+2)+p1,ans*(n+2)+p2,p[i]);
        }
        k-=ISAP();if(k<=0)return printf("%d
",ans),void();
        
    }
}

## 第十四题:孤岛营救

(BSOJ2554)——状压 分层图最短路

题意

一个(n*m)的方格图,有些临近格子之间需要用钥匙(或者不能通行)花费(1)个时间通行,格子里有钥匙,(钥匙数(le 10)),求((1,1))((n,m))最短时间

Solution

作为分层图最短路模板题,讲一下方法

首先钥匙数小,我们以手上持有的钥匙为状态,用状态来分层

状态之间跑最短路转移即可

注意,这题显式建分层图比较麻烦所以模拟状压分层

Code

inline int SPFA(void){
    re int i,j,x,y,lay;re deque<Node>q;S=1,T=pos(n,m);
    for(i=S;i<=T;++i)for(j=1;j<1<<(p+1);++j)dis[j][i]=INF;
    dis[1][S]=0,vis[1][S]=1;q.push_front((Node){1,S});
    while(!q.empty()){
        lay=q.front().layer;x=q.front().pos,q.pop_front(),vis[lay][x]=0;
        dis[lay|key[x]][x]=min(dis[lay|key[x]][x],dis[lay][x]);lay|=key[x];
        for(i=h[x];i;i=e[i].next)if(lay&e[i].v){
            y=e[i].to;
            if(dis[lay][y]>dis[lay][x]+1){
                dis[lay][y]=dis[lay][x]+1;
                if(vis[lay][y])continue;vis[lay][y]=1;
                if(q.size())
                    if(dis[lay][y]<dis[q.front().layer][q.front().pos])q.push_front((Node){lay,y});
                    else q.push_back((Node){lay,y});
                else q.push_back((Node){lay,y});
            }
        }
    }
    res=INF;
    for(i=1;i<(1<<(p+1));++i)res=min(res,dis[i][T]);
    return res^INF?res:-1;
}

## 第十五题:汽车加油行驶

(BSOJ2555)——分层图最短路

题意

一个(n*m)的方格图,到每个格子可以花费(p)加满油(上界(a)),四联通格子可以在一个油量的消耗下移动,求((1,1))((n,m))最小花费

Solution

用消耗油量分层,不用状压了,可以一次性建出来图

Code

inline int SPFA(void){
    re int i,j,x,y;re deque<int> q;
    S=1;
    for(i=1;i<=n*n*(m+1);++i)dis[i]=INF;dis[S]=0,vis[S]=1;q.push_front(S);
    while(!q.empty()){
        x=q.front(),q.pop_front(),vis[x]=0;
        for(i=h[x];i;i=e[i].next){
            y=e[i].to;
            if(dis[y]>dis[x]+e[i].v){
                dis[y]=dis[x]+e[i].v;
                if(vis[y])continue;vis[y]=1;
                if(q.size())
                    if(dis[y]<dis[q.front()])q.push_front(y);
                    else q.push_back(y);
                else q.push_back(y);
            }
        }
    }
    res=INF;
    for(i=1;i<=m+1;++i)res=min(res,dis[n*n*i]);
    return res;
}
inline void Read(void){
    re int i,j,k,tx,ty,dir,x;n=read(),m=read(),a=read(),b=read(),c=read();
    for(i=1;i<=n;++i)
        for(j=1;j<=n;++j){
            x=read();
            for(k=1;k<=m;++k)AddEdge(i,j,k,i,j,k+1,0);
            if(x){
                for(k=2;k<=m+1;++k)AddEdge(i,j,k,i,j,1,a);
                for(dir=0;dir<4;++dir){
                    tx=i+dx[dir],ty=j+dy[dir];
                    if(tx<1||tx>n||ty<1||ty>n)continue;
                    if(tx>=i&&ty>=j)AddEdge(i,j,1,tx,ty,2,0);else AddEdge(i,j,1,tx,ty,2,b);
                }
            }
            else{
                for(k=1;k<=m;++k){
                    for(dir=0;dir<4;++dir){
                        tx=i+dx[dir],ty=j+dy[dir];
                        if(tx<1||tx>n||ty<1||ty>n)continue;
                        if(tx>=i&&ty>=j)AddEdge(i,j,k,tx,ty,k+1,0);else AddEdge(i,j,k,tx,ty,k+1,b);
                    }
                }
                for(k=2;k<=m+1;++k)AddEdge(i,j,k,i,j,1,a+c);
            }
        }
}

## 第十六题:数字梯形问题

(BSOJ2556)——分层图最短路

题意

给出一个(n)层分别为(m,m+1,cdots,m+n-1)个数的梯形,分别求

  • 从梯形的顶至底的(m)条路径互不相交。
  • 从梯形的顶至底的(m)条路径仅在数字结点处相交。
  • 从梯形的顶至底的(m)条路径允许在数字结点相交或边相交。
    这些路径的最大和

Solution

首先拆点(((x,y),(x,y)'))

((S,(1,i),1,0))从第一行出发
对点
(((x,y),(x,y)',点可以被走多少次,v_{x,y}))
对边
(((x,y)',(x+1,y),边可以被走多少次,0))
(((x,y)',(x+1,y+1),边可以被走多少次,0))
(((n,i)',T,INF,0))结束

Code

inline void Build(re int x,re int y){
    re int i,j;cnt=1,memset(h,0,P<<2);
    for(i=1;i<=m;++i)AddEdge(S,(pos(1,i)<<1)-1,1,0);
    for(i=1;i<n;++i){
        for(j=1;j<=i+m-1;++j){
            AddEdge((pos(i,j)<<1)-1,pos(i,j)<<1,x,v[i][j]);
            AddEdge(pos(i,j)<<1,(pos(i+1,j)<<1)-1,y,0);
            AddEdge(pos(i,j)<<1,(pos(i+1,j+1)<<1)-1,y,0);
        }
    }
    for(i=1;i<=n+m-1;++i)AddEdge(pos(n,i)<<1,T,INF,0),AddEdge((pos(n,i)<<1)-1,pos(n,i)<<1,x,v[n][i]);
}
inline void Solve(void){
    Build(1,1),Dinic(),printf("%d
",ansc);
    Build(INF,1),Dinic(),printf("%d
",ansc);
    Build(INF,INF),Dinic(),printf("%d
",ansc);
}

## 第十七题:运输问题

(BSOJ2557)——费用流

题意

问一个网络流模型点权费用流最小/最大和

Solution

拆点后裸的费用流

Code

inline void Build(re char dlt){
    re int i,j;
    memset(h,0,P<<2);cnt=1;
    for(i=1;i<=n;++i)AddEdge(S,i,a[i],0);
    for(i=1;i<=m;++i)AddEdge(i+n,T,b[i],0);
    for(i=1;i<=n;++i)
        for(j=1;j<=m;++j)AddEdge(i,n+j,INF,dlt*w[i][j]);
}
inline void Solve(void){
    Build(1);Dinic();printf("%d
",ansc);
    Build(-1);Dinic();printf("%d
",-ansc);
}

## 第十八题:分配问题

(BSOJ2558)——费用流

题意

问一个二分图最佳匹配

Solution

二分图最佳匹配转费用流

建图

((S,i,1,0))
((i,j',1,w_{i,j'}))
((i',T,1,0))

Code

## 第十九题:负载平衡问题

(BSOJ2559)——最小代价供求 费用流

题意

(n)个仓库环状排列,只能在相邻仓库间运送,求最少运货量使(n)个仓库货量相同

Solution

首先这就是均分纸牌一个经典的中位数贪心
同时也是费用流模型最小代价供求
首先我们给每个点(-ave),问题转化为让每个点为(0)的最小流动量
建图:
即多的要流走那么多((S,i,a_i,0))
少的要留来那么多((i,T,-a_i,0))
两两间可以传递((i,i+1,INF,1))
最小费用流即可

Code

for(i=1;i<=n;++i){scanf("%d",a+i);sum+=a[i];}sum/=n;
for(i=1;i<=n;++i){a[i]-=sum;}
for(i=1;i<=n;++i){
    if(a[i]>0)AddEdge(s,i,a[i],0);
    else if(a[i]<0)AddEdge(i,t,-a[i],0);
}
for(i=1;i<n;++i){AddEdge(i,i+1,INF,1);AddEdge(i+1,i,INF,1);}AddEdge(1,n,INF,1);AddEdge(n,1,INF,1);

## 第二十题:负载平衡问题

(BSOJ2560)——最小代价供求 费用流

题意

((n+1)*(m+1))的方格,四联通,每个点有点权,每个机器人有出发位置和目的地,求点权和最大值

Solution

其实很裸的最大费用最大流
建图:
对每个机器人((S,(x_i,y_i),INF,0)~((x_i,y_i),T,INF,0))
因为沿途生物标本由最先遇到它的深海机器人完成采集,所以落脚点只有一次成为采集点(((x1,y1),(x2,y2),1,c_{x1,y1,x2,y2})),其余(((x1,y1),(x2,y2),INF,0))
跑最大费用最大流即可

Code

inline void Read(void){
    re int i,j,x,y,k;
    scanf("%d%d%d%d",&A,&B,&n,&m);s=pos(0,0)-1;t=pos(n,m)+1;
    for(i=0;i<=n;++i)
        for(j=0;j<m;++j){scanf("%d",&k);x=pos(i,j);y=pos(i,j+1);AddEdge(x,y,1,-k);AddEdge(x,y,INF,0);}
    for(j=0;j<=m;++j)
        for(i=0;i<n;++i){scanf("%d",&k);x=pos(i,j);y=pos(i+1,j);AddEdge(x,y,1,-k);AddEdge(x,y,INF,0);}
    for(i=1;i<=A;++i){scanf("%d%d%d",&k,&x,&y);AddEdge(s,pos(x,y),k,0);}
    for(i=1;i<=B;++i){scanf("%d%d%d",&k,&x,&y);AddEdge(pos(x,y),t,k,0);}
}

## 第二十一题:最长k可重区间集问题

(BSOJ2561)——费用流 离散化

题意

给出(n)个区间,每个区间权值为区间长度.求一种取区间的方案,使得每个点最多被取(k)次,且权值和最大.

Solution

建图
对每个点((p_i,p_{i+1},k,0))(离散化后点的位置)
对每个区间((l_i,r_i,1,len_i))
求最大费用最大流即可

Code

inline void Read(void){
    re int i,j;n=read(),k=read(),S=0;
    for(i=1;i<=n;++i){
        a[i]=(Seg){read(),read()};if(a[i].l>a[i].r)swap(a[i].l,a[i].r);
        x[++len]=a[i].l,x[++len]=a[i].r;
    }
    sort(x+1,x+len+1);len=unique(x+1,x+len+1)-x-1;T=len+1,P=T+1;
    for(i=1;i<=n;++i)a[i].l=lower_bound(x+1,x+len+1,a[i].l)-x,a[i].r=lower_bound(x+1,x+len+1,a[i].r)-x;
    for(i=1;i<len;++i)AddEdge(i,i+1,INF,0);
    AddEdge(S,1,k,0),AddEdge(len,T,k,0);
    for(i=1;i<=n;++i)AddEdge(a[i].l,a[i].r,1,x[a[i].r]-x[a[i].l]);
}

## 第二十二题:最长k可重线段集问题

(BSOJ2562)——费用流 离散化 拆点

题意

给出(n)个线段,每个区间权值为线段长度.求一种取线段的方案,使得每个点最多被取(k)次,且权值和最大.

Solution

基础看上一题
我们直接把线段映射到(x)轴上,特例是线段垂直于(x)轴的
那么我们做一个转化,把一个点拆成两个,有重复落入的时候就放到一二个,没有时就放第二个

Code

类似的

inline void Read(void){
    re int i,x1,y1,x2,y2,t;n=read(),k=read(),S=0;
    for(i=1;i<=n;++i){
        x1=read(),y1=read(),x2=read(),y2=read();if(x1>x2)std::swap(x1,x2);
        t=(int)sqrt(1ll*(x1-x2)*(x1-x2)+1ll*(y1-y2)*(y1-y2));
        x1*=2;x2*=2;if(x1==x2)x2++;else x1++;
        a[i]=(Seg){x1,x2,t};
        x[++len]=a[i].l,x[++len]=a[i].r;
    }
    std::sort(x+1,x+len+1);len=std::unique(x+1,x+len+1)-x-1;T=len+1,P=T+1;
    for(i=1;i<=n;++i)a[i].l=std::lower_bound(x+1,x+len+1,a[i].l)-x,a[i].r=std::lower_bound(x+1,x+len+1,a[i].r)-x;
    for(i=1;i<len;++i)AddEdge(i,i+1,INF,0);
    AddEdge(S,1,k,0),AddEdge(len,T,k,0);
    for(i=1;i<=n;++i)AddEdge(a[i].l,a[i].r,1,a[i].len);
}

## 第二十三题:火星探险问题

(BSOJ2563)——(oj)暂无(SPJ)

## 第二十四题:骑士共存问题

(BSOJ2564)——黑白染色 二分图最大独立集

题意

给出一个(n*n)的棋盘和不能下子的位置

Solution

首先黑白染色
有限制的点一定为不同颜色集合,互相连边
最大独立集=点权和-最小独立集(最小割)
跑最大流即可

Code

以上是关于网络流24题小结的主要内容,如果未能解决你的问题,请参考以下文章

[网络流24题] 运输问题

网络流24题 软件补丁问题

网络流24题 软件补丁问题

网络流24题-飞行员配对方案问题-二分图最大匹配

网络流24题 飞行员配对方案问题

网络流24题 飞行员配对方案问题