网络流1-5

1.飞行员配对方案

思路:

二分图最大匹配问题。

匈牙利好写一点,而且自带记录匹配对象。
但是既然练网络流就用网络流写吧。

建图:
源点连接左半部,汇点连接右半部,中间二分图,边权都为1。

在残余网络中找匹配对象:
利用前向星的成对变换遍历所有边和其反向边,如果当前遍历到的边不是与源点和汇点连接的边,则为二分图中间边,如果反向边边权不为0,即为匹配边(只有有流的边反向边不为0),该边的两端点就是一对答案。

ps:
题目没说有多少边,边数组要开大一点否则re

code:
#include
using namespace std;
const int maxm=105+5;
const int inf=1e9;
int head[maxm],nt[maxm<<5],to[maxm<<5],w[maxm<<5],cnt;//题目没说一共有多少边,太坑了
int d[maxm];
int m,n;
int s,t;
int maxflow;
void init(){
    memset(head,-1,sizeof head);
    cnt=1;
}
void add(int x,int y,int z){
    cnt++;nt[cnt]=head[x];head[x]=cnt;to[cnt]=y;w[cnt]=z;
}
bool bfs(){
    memset(d,0,sizeof d);
    queue<int>q;
    q.push(s);
    d[s]=1;
    while(!q.empty()){
        int x=q.front();
        q.pop();
        for(int i=head[x];i!=-1;i=nt[i]){
            int v=to[i];
            if(!d[v]&&w[i]){
                d[v]=d[x]+1;
                q.push(v);
                if(v==t)return 1;
            }
        }
    }
    return 0;
}
int dfs(int x,int flow){
    if(x==t)return flow;
    int res=flow;
    for(int i=head[x];i!=-1;i=nt[i]){
        int v=to[i];
        if(w[i]&&d[v]==d[x]+1){
            int k=dfs(v,min(res,w[i]));
            if(k){
                w[i]-=k;
                w[i^1]+=k;
                res-=k;
            }else{
                d[v]=-1;
            }
            if(!res)break;
        }
    }
    return flow-res;
}
void dinic(){
    maxflow=0;
    while(bfs()){
        maxflow+=dfs(s,inf);
    }
}
signed main(){
    init();
    scanf("%d%d",&m,&n);
    s=0,t=n+1;
    for(int i=1;i<=m;i++){//源点到外籍飞行员
        add(s,i,1);
        add(i,s,0);
    }
    for(int i=m+1;i<=n;i++){//英国飞行员到汇点
        add(i,t,1);
        add(t,i,0);
    }
    int a,b;
    while(1){
        scanf("%d%d",&a,&b);
        if(a==-1&&b==-1)break;
        add(a,b,1);//a到b
        add(b,a,0);
    }
    dinic();
    if(maxflow==0){//如果无解
        puts("No solution");
    }else{
        printf("%d\n",maxflow);
        for(int x=1;x<=m;x++){//遍历连接源点的点
            for(int i=head[x];i!=-1;i=nt[i]){
                if(w[i^1]){//如果反向边不为0说明是匹配边(有流量的边反向边才不为0)
                    if(to[i]==s||to[i]==t)continue;
                    printf("%d %d\n",x,to[i]);
                    break;
                }
            }
        }
    }
    return 0;
}
//https://www.luogu.com.cn/problem/P2756

2.太空飞行计划问题

思路:

最大权闭合回路问题。

建图:
源点到实验,边权为收入
仪器到汇点,边权为成本
实验到实验所需的仪器,边权为inf

答案为实验收入和减去最小割。

求选择的实验和仪器:
最后残余网络中和源点连接的时选择的实验,和汇点连接的是选择的仪器。
正常来说可以dfs求解,但是我用dfs没跑出样例答案来,不知道为啥
看别人说用最后一次的层次数组d就可以求出答案:
如果d[i]为正值说明i点有流,所以判断实验和仪器的d[i]是否大于0就可以知道是否被选择

code:
#include
using namespace std;
const int maxm=1e4+5;
const int inf=1e9;
int head[maxm],nt[maxm<<5],to[maxm<<5],w[maxm<<5],cnt;
int d[maxm];
int m,n;
int s,t;
int maxflow;
void init(){
    memset(head,-1,sizeof head);
    cnt=1;
}
void add(int x,int y,int z){
    cnt++;nt[cnt]=head[x];head[x]=cnt;to[cnt]=y;w[cnt]=z;
}
bool bfs(){
    memset(d,0,sizeof d);
    queue<int>q;
    q.push(s);
    d[s]=1;
    while(!q.empty()){
        int x=q.front();
        q.pop();
        for(int i=head[x];i!=-1;i=nt[i]){
            int v=to[i];
            if(!d[v]&&w[i]){
                d[v]=d[x]+1;
                if(v==t)return 1;
                q.push(v);
            }
        }
    }
    return 0;
}
int dfs(int x,int flow){
    if(x==t)return flow;
    int res=flow;
    for(int i=head[x];i!=-1;i=nt[i]){
        int v=to[i];
        if(w[i]&&d[v]==d[x]+1){
            int k=dfs(v,min(res,w[i]));
            if(k){
                w[i]-=k;
                w[i^1]+=k;
                res-=k;
            }else{
                d[v]=-1;
            }
            if(!res)break;
        }
    }
    return flow-res;
}
void dinic(){
    maxflow=0;
    while(bfs()){
        maxflow+=dfs(s,inf);
    }
}

signed main(){
    init();
    scanf("%d%d",&m,&n);//m个实验,n个仪器
    s=0,t=m+n+1;
    int sum=0;//记录总收入
    for(int i=1;i<=m;i++){
        int get;//赞助商支付的费用,即收入
        scanf("%d",&get);
        sum+=get;
        //
        add(s,i,get);//源点到正权点
        add(i,s,0);
        //
        char ch=getchar();
        while(ch!='\n'&&ch!='\r'){//洛谷的这题输入方式不太一样
            int x;//仪器编号
            scanf("%d",&x);
            //
            add(i,m+x,inf);//实验到仪器的边权为inf
            add(m+x,i,0);
            //
            ch=getchar();
        }
    }
    for(int i=1;i<=n;i++){
        int v;//仪器的成本,为负权
        scanf("%d",&v);
        //
        add(m+i,t,v);//负权点到汇点
        add(t,m+i,0);
        //
    }
    dinic();
    for(int i=1;i<=m;i++){
        if(d[i]>0){//d[i]>0说明i被选择
            printf("%d ",i);
        }
    }
    puts("");
    for(int i=1;i<=n;i++){
        if(d[m+i]>0){
            printf("%d ",i);
        }
    }
    puts("");
    printf("%d\n",sum-maxflow);
    return 0;
}
//https://www.luogu.com.cn/problem/P2762

3.最小路径覆盖问题

思路:

最小路径覆盖问题

建图:
对于每个点a,拆成入点a和出点aa
对于每条边x->y,建边yy->x (y的出点连x的入点)
源点连接每个出点,汇点连接每个出点,所有边权值为1

最少路径数:
最大流中每个流都表明两个点被合并了
所以点数n减去最大流maxflow就是剩下的点数,即需要的最少路径数

求路径:
我用的方法是遍历链式前向星中所有出点所在的边的反向边,
如果反向边的边权不为0,则说明有流,则为匹配边。
用数组pre记录每个点的前驱,link记录每个点的后继
对于每个匹配边x->y,pre[y]=x,link[x]=y
然后for循环从1到n找pre为0的点,则该点为路径起点,
用link数组从这个点开始不断输出路径,直到link为0,说明路径结束
具体操作可以看代码

code:
#include
using namespace std;
const int maxm=1e4+5;
const int inf=1e9;
int head[maxm],nt[maxm<<3],to[maxm<<3],w[maxm<<3],cnt;
int pre[maxm];
int link[maxm];
int d[maxm];
int n,m;
int s,t;
int maxflow;
void init(){
    memset(head,-1,sizeof head);
    cnt=1;
}
void add(int x,int y,int z){
    cnt++;nt[cnt]=head[x];head[x]=cnt;to[cnt]=y;w[cnt]=z;
}
bool bfs(){
    memset(d,0,sizeof d);
    queue<int>q;
    q.push(s);
    d[s]=1;
    while(!q.empty()){
        int x=q.front();
        q.pop();
        for(int i=head[x];i!=-1;i=nt[i]){
            int v=to[i];
            if(!d[v]&&w[i]){
                d[v]=d[x]+1;
                if(v==t)return 1;
                q.push(v);
            }
        }
    }
    return 0;
}
int dfs(int x,int flow){
    if(x==t)return flow;
    int res=flow;
    for(int i=head[x];i!=-1;i=nt[i]){
        int v=to[i];
        if(w[i]&&d[v]==d[x]+1){
            int k=dfs(v,min(res,w[i]));
            if(k){
                w[i]-=k;
                w[i^1]+=k;
                res-=k;
            }else{
                d[v]=-1;
            }
            if(!res)break;
        }
    }
    return flow-res;
}
void dinic(){
    maxflow=0;
    while(bfs()){
        maxflow+=dfs(s,inf);
    }
}
signed main(){
    init();
    scanf("%d%d",&n,&m);
    s=0,t=n+n+1;
    /*
        源点0
        汇点n+n+1
        出点1-n
        入点(n+1)-(n+n)
    */
    for(int i=1;i<=n;i++){//源点到出点
        add(s,i,1);
        add(i,s,0);
    }
    for(int i=n+1;i<=n+n;i++){//入点到汇点
        add(i,t,1);
        add(t,i,0);
    }
    for(int i=1;i<=m;i++){
        int a,b;
        scanf("%d%d",&a,&b);
        add(a,b+n,1);//a的出点连接b的入点
        add(b+n,a,0);
    }
    dinic();
    for(int x=1;x<=n;x++){//遍历出点
        for(int i=head[x];i!=-1;i=nt[i]){
            if(w[i^1]){//反向边不为0说明有流
                if(to[i]==s||to[i]==t)continue;//跳过连接源点和汇点的
                pre[to[i]-n]=x;//把对面端点的前驱设置为i
                link[x]=to[i]-n;//当前点的后继设置为对面端点
            }
        }
    }
    for(int i=1;i<=n;i++){
        if(!pre[i]){
            int t=i;
            while(t){
                printf("%d ",t);
                t=link[t];
            }
            puts("");
        }
    }
    printf("%d\n",n-maxflow);
    /*
        最大流中的每个流都表明两个点被合并了
        所以点数n减去最大流maxflow就是剩下的点数,即需要的最少路径数
    */
    return 0;
}
//https://www.luogu.com.cn/problem/P2764

4.魔术球问题

思路:

可以转化为最小路径覆盖问题

可以放在一起的球的方案看成点合并,最大流即为合并的点,就是最小路径覆盖
把每个球要拆成入点和出点,然后和为平方的建边(小编号的连向大编号)
球的个数减去最大流就是所需要的路径数,在这题里面也就是需要的柱子数

当柱子数超过的时候,去掉最后一次加的球,然后重新建图跑最大流就行了

需要注意的地方:
1.如果每次加一个球都重新建图会超时,所以每添加一个球在原图的基础上建图就行了
2.因为原图是跑过dinic的,所以是残余网络,在残余网络上加边然后再跑dinic之后,跑出来的最大流是流的增加量,所以再开一个变量记录总的流量
3.我路径的记录的方法和上面一篇的最小路径覆盖差不多,具体操作看代码

code:
#include
using namespace std;
const int maxm=1e4+5;
const int inf=1e9;
int head[maxm],nt[maxm<<5],to[maxm<<5],w[maxm<<5],cnt;
int in[maxm],out[maxm],id[maxm];//
int pre[maxm],link[maxm];//
int d[maxm];
int n;
int s,t;
int maxflow;
void init(){
    memset(head,-1,sizeof head);
    cnt=1;
}
void add(int x,int y,int z){
    cnt++;nt[cnt]=head[x];head[x]=cnt;to[cnt]=y;w[cnt]=z;
}
bool bfs(){
    memset(d,0,sizeof d);
    queue<int>q;
    q.push(s);
    d[s]=1;
    while(!q.empty()){
        int x=q.front();
        q.pop();
        for(int i=head[x];i!=-1;i=nt[i]){
            int v=to[i];
            if(!d[v]&&w[i]){
                d[v]=d[x]+1;
                if(v==t)return 1;
                q.push(v);
            }
        }
    }
    return 0;
}
int dfs(int x,int flow){
    if(x==t)return flow;
    int res=flow;
    for(int i=head[x];i!=-1;i=nt[i]){
        int v=to[i];
        if(w[i]&&d[v]==d[x]+1){
            int k=dfs(v,min(res,w[i]));
            if(k){
                w[i]-=k;
                w[i^1]+=k;
                res-=k;
            }else{
                d[v]=-1;
            }
            if(!res)break;
        }
    }
    return flow-res;
}
void dinic(){
    maxflow=0;
    while(bfs()){
        maxflow+=dfs(s,inf);
    }
}
signed main(){
    init();
    //
    scanf("%d",&n);
    int tot=0;
    s=++tot;
    t=++tot;
    int now=0;
    int flow=0;
    while(now-flow<=n){
        now++;
        out[now]=++tot;id[tot]=now;//赋予编号,且记录编号对应的球的id
        in[now]=++tot;id[tot]=now;
        add(s,out[now],1);add(out[now],s,0);
        add(in[now],t,1);add(t,in[now],0);
        for(int i=1;i<now;i++){
            int sum=i+now;
            int sq=(int)sqrt(sum);
            if(sq*sq==sum){
                add(out[i],in[now],1);add(in[now],out[i],0);
            }
        }
        dinic();
        flow+=maxflow;
    }
    now--;//去掉不成立的最后一个球
    init();//清空图
    for(int i=1;i<=now;i++){//连接源点和汇点
        add(s,out[i],1);add(out[i],s,0);
        add(in[i],t,1);add(t,in[i],0);
    }
    for(int i=1;i<=now;i++){
        for(int j=i+1;j<=now;j++){
            int sum=i+j;
            int sq=(int)sqrt(sum);
            if(sq*sq==sum){
                add(out[i],in[j],1);add(in[j],out[i],0);
            }
        }
    }
    dinic();
    printf("%d\n",now);
    for(int x=1;x<=now;x++){//枚举每个球
        for(int i=head[out[x]];i!=-1;i=nt[i]){//遍历出点
            if(w[i^1]){//如果反向边有流说明为匹配边
                if(to[i]==s||to[i]==t)continue;
                pre[id[to[i]]]=x;
                link[x]=id[to[i]];
            }
        }
    }
    for(int i=1;i<=now;i++){
        if(!pre[i]){
            int t=i;
            while(t){
                printf("%d ",t);
                t=link[t];
            }
            puts("");
        }
    }
    return 0;
}
//https://www.luogu.com.cn/problem/P2765

5.圆桌问题

思路:

二分图多重匹配问题

这题比较简单,建图如下:
源点连接每个单位,容量为单位代表人数,
每个圆桌连接汇点,容量为圆桌的容量,
每个单位连接每个圆桌,容量为1

建完图跑最大流就行了

code:
#include
using namespace std;
const int maxm=1e4+5;
const int inf=1e9;
int head[maxm],nt[maxm<<5],to[maxm<<5],w[maxm<<5],cnt;
int d[maxm];
int m,n;
int s,t;
int maxflow;
void init(){
    memset(head,-1,sizeof head);
    cnt=1;
}
void add(int x,int y,int z){
    cnt++;nt[cnt]=head[x];head[x]=cnt;to[cnt]=y;w[cnt]=z;
}
bool bfs(){
    memset(d,0,sizeof d);
    queue<int>q;
    q.push(s);
    d[s]=1;
    while(!q.empty()){
        int x=q.front();
        q.pop();
        for(int i=head[x];i!=-1;i=nt[i]){
            int v=to[i];
            if(!d[v]&&w[i]){
                d[v]=d[x]+1;
                if(v==t)return 1;
                q.push(v);
            }
        }
    }
    return 0;
}
int dfs(int x,int flow){
    if(x==t)return flow;
    int res=flow;
    for(int i=head[x];i!=-1;i=nt[i]){
        int v=to[i];
        if(w[i]&&d[v]==d[x]+1){
            int k=dfs(v,min(res,w[i]));
            if(k){
                w[i]-=k;
                w[i^1]+=k;
                res-=k;
            }else{
                d[v]=-1;
            }
            if(!res)break;
        }
    }
    return flow-res;
}
void dinic(){
    maxflow=0;
    while(bfs()){
        maxflow+=dfs(s,inf);
    }
}
signed main(){
    init();
    scanf("%d%d",&m,&n);
    s=0,t=m+n+1;
    int sum=0;//统计总人数
    for(int i=1;i<=m;i++){
        int x;
        scanf("%d",&x);
        sum+=x;
        add(s,i,x);//源点连接单位,容量为代表人数
        add(i,s,0);
    }
    for(int i=m+1;i<=m+n;i++){
        int x;
        scanf("%d",&x);
        add(i,t,x);//餐桌连接汇点,容量为餐桌容量
        add(t,i,0);
    }
    for(int i=1;i<=m;i++){
        for(int j=m+1;j<=m+n;j++){
            add(i,j,1);//每个单位对每个餐桌连边,容量为1
            add(j,i,0);
        }
    }
    dinic();
    if(maxflow!=sum){//如果最大流不等于总人数说明无解
        puts("0");
    }else{//否则有解
        puts("1");
        for(int x=1;x<=m;x++){//遍历每个单位
            for(int i=head[x];i!=-1;i=nt[i]){
                if(w[i^1]){//反向边有流说明是匹配边
                    if(to[i]==s||to[i]==t)continue;//跳过和源点汇点连接的边
                    printf("%d ",to[i]-m);
                }
            }
            puts("");
        }
    }
    return 0;
}
//https://www.luogu.com.cn/problem/P3254

你可能感兴趣的:(网络流1-5)