网络流 最大流问题入门选作 (POJ1273,BZOJ1711)

算法

对于网络流的最大流问题,笔者比较偏向于使用Dinic算法(其实是因为笔者实在是弱只会一种最大流算法),所以接下来的两题使用的都是Dinic算法。

为了方便入门(或是自己以后回忆),在此介绍一下Dinic算法的流程:

  1. 列表内容
  2. 进行一次bfs,并确定是否还有能够从源点S到汇点T的流,如果有的话,进入步骤2,否则跳至步骤4;
  3. 进行一次增广操作,并将流过的边的可用流量减去流过的流量,同时建一条流量为这条边所流过的流量的反向边;
  4. 将这条流加入答案;
  5. 算法结束,返回答案。

例题选作

~

POJ 1273 草地排水 Drainage Ditches

题目大意

给出一个有向图,并注明有向图中每一条边的最大流量,以及图中的源点和汇点,要算出这个图的最大流。

思路&题解

这就是一道很明显的最大流问题,用于检验算法的正确性(练手)与入门,这也是我当时网络流问题的人生第一题。直接套用上方的Dinic算法流程即可。

代码
#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;

const int maxn=205;
const int INF=int(1e9+7);

struct Edge {
    int from,to;
    int next;
    int w;
}eage[maxn*2];
int head[maxn];
int tot=-1;

void add(int x,int y,int z) {
    eage[++tot].from=x;
    eage[tot].to=y;
    eage[tot].w=z;
    eage[tot].next=head[x];
    head[x]=tot;
    return;
}

int n,m;
int dis[maxn];
bool used[maxn];

queue <int> que;
bool bfs() {
    while(que.size()) que.pop();
    memset(dis,0,sizeof dis);
    memset(used,0,sizeof used);

    dis[1]=1;
    used[1]=true;
    que.push(1);

    while(que.size()) {
        int u=que.front();
        que.pop();
        for(int i=head[u];~i;i=eage[i].next) if(!used[eage[i].to] && eage[i].w) {
            int v=eage[i].to;
            used[v]=true;
            dis[v]=dis[u]+1;
            que.push(v);
        }
    }

    if(used[n]) return true;
    else return false;
}

int dfs(int k,int v) {
    if(k==n || v==0) return v;
    int ret=0;
    for(int i=head[k];~i;i=eage[i].next) if(dis[eage[i].to]==dis[k]+1 && eage[i].w) {
        int cur=eage[i].to;
        int f=dfs(cur,min(eage[i].w,v));
        eage[i].w-=f;
        eage[i^1].w+=f;
        v-=f;
        ret+=f;
        if(!v) break;
    }
    if(!ret) dis[k]=-1;
    return ret;
}

int dinic() {
    int res=0;
    while(bfs()) res+=dfs(1,INF);
    return res;
}

int main() {
    scanf("%d%d",&m,&n);
    memset(head,-1,sizeof head);

    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,0);
    }

    printf("%d\n",dinic());
    return 0;
}

BZOJ 1711 吃饭 Dining

题目大意

共有n头牛,每一头牛有一种自己喜欢的饮料和食物,每一种饮料或食物只能供一头牛食用,问最多能够满足多少头牛的需求。

思路&题解

这道题貌似拿二分图最大匹配也是可以做的,如果有一头牛同时喜欢某种食物和饮料的话就在这个食物和饮料之间连一条边,用匈牙利算法跑一遍二分图的最大匹配即可。

而今天想要介绍的并不是二分图匹配算法而是网络流算法。因为每一头牛只能被满足一次,所以说这个限制条件就相当于每一头牛的流量为1,而某个点是不能设置流量的,所以我们将一头牛拆成两个点来考虑,这两个点之间的流量是1。而因为每一种食物或饮料至多被选择1次,所以直接向原点或汇点连一条流量为1的边就可以。建图完成后,最大流的值即为能满足的牛的数量。

代码
#include<cstdio>
#include<queue>
#include<cstring>
#include<iostream>
using namespace std;

#define food(k) (1+(k))
#define drink(k) ((1+f+n*2)+(k))
#define cow1(k) ((1+f)+(k))
#define cow2(k) (cow1(k)+n)

const int INF=int(1<<30);
const int maxn=5000+5;

struct Edge {
    int from,to;
    int w;
    int next;
}eage[maxn*10];
int head[maxn];
int tot=-1;
void add(int x,int y,int z) {
    eage[++tot].from=x;
    eage[tot].to=y;
    eage[tot].w=z;
    eage[tot].next=head[x];
    head[x]=tot;
    return;
}

int S,T;
int n,d,f;
bool used[maxn];
int dis[maxn];
queue <int> que;

bool bfs() {
    while(que.size()) que.pop();
    memset(used,0,sizeof used);
    memset(dis,0,sizeof dis);

    dis[S]=1;
    used[S]=true;
    que.push(S);

    while(que.size()) {
        int u=que.front();
        que.pop();
        for(int i=head[u];~i;i=eage[i].next) if(!used[eage[i].to] && eage[i].w) {
            int v=eage[i].to;
            used[v]=true;
            dis[v]=dis[u]+1;
            que.push(v);
        }
    }

    if(!used[T]) return false;
    else return true;
}

int dfs(int k,int v) {
    if(k==T || v==0) return v;
    int ret=0;
    for(int i=head[k];~i;i=eage[i].next) if(eage[i].w && dis[eage[i].to]==dis[k]+1) {
        int now=eage[i].to;
        int f=dfs(now,min(eage[i].w,v));
        ret+=f;
        v-=f;
        eage[i].w-=f;
        eage[i^1].w+=f;
        if(!v) break;
    }
    if(!ret) dis[k]=-1;
    return ret;
}

int dinic() {
    int res=0;
    while(bfs()) res+=dfs(S,INF);
    return res;
}

int main() {
    memset(head,-1,sizeof head);
    scanf("%d%d%d",&n,&f,&d);

    S=1;
    T=drink(d)+1;

    for(int i=1;i<=f;i++)
        add(S,food(i),1),
        add(food(i),S,0);
    for(int i=1;i<=d;i++)
        add(drink(i),T,1),
        add(T,drink(i),0);


    for(int i=1;i<=n;i++) {
        add(cow1(i),cow2(i),1);
        add(cow2(i),cow1(i),0);

        int x,y;
        scanf("%d%d",&x,&y);
        for(int j=1;j<=x;j++) {
            int a;
            scanf("%d",&a);
            add(food(a),cow1(i),1);
            add(cow1(i),food(a),0);
        }
        for(int j=1;j<=y;j++) {
            int a;
            scanf("%d",&a);
            add(cow2(i),drink(a),1);
            add(drink(a),cow2(i),0);
        }
    }

    printf("%d",dinic());
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值