网络流例题总结

本文精选了多个网络流算法的经典题目,包括最大流、最小割等问题,详细解析了每道题目的解题思路和核心代码实现,涵盖裸流、最小费用流、最大流的应用场景等,帮助读者深入理解网络流算法。

最大流相关例题


POJ 3436

题意:

生产1台电脑需要n个机器,每个机器对运过来的未加工好的机器加工,每个机器每小时有最大的生产电脑量,每台电脑有p个部分,且每台机器只会接受满足条件的未加工好的电脑,问一小时内最大的生产量?

思路:

属于比较裸的最大流,按照条件建边即可,注意每一台机器有最大的生产量C,这个可以当做流入这台机器的最大流量,但是考虑特殊情况,我们必须去拆点才能建立正确的图,也就是每台机器拆成两个点,且这两个点之间的边的最大流量为C。E-K模板 get

int cap[N][N], flow[N][N];

int EdmondsKarp(int s, int t) {
    int p[N], f[N];
    queue<int> q;

    memset(flow, 0, sizeof(flow));
    int ans = 0;

    while(true) {
        memset(f, 0, sizeof(f));

        f[s] = INF;

        q.push(s);

        while(!q.empty()) { //BFS 找增广路
            int u = q.front(); q.pop();

            for(int v=1; v<=n; v++) if(!f[v] && cap[u][v]>flow[u][v]){
                //找到新节点v
                p[v] = u; q.push(v);
                f[v] = min(f[u], cap[u][v]-flow[u][v]);
            }
        }

        if(f[t] == 0) break;    //找不到,则当前流已经是最大流

        for(int u=t; u != s; u = p[u]) {    //从汇点往回走
            flow[p[u]][u] += f[t];  //更新正向流量
            flow[u][p[u]] -= f[t];  //更新反向流量
        }

        ans += f[t];  //更新从 s 流出的流量
    }

    return ans;
}

POJ 3281

题意:

有N头牛,每头牛只能吃一份含特定食物及饮料的套餐,且每种食物和饮料只能被一头牛吃,问最大能喂多少头牛?

思路:

一开始看上去以为是二分图匹配,但是明显分成两个图去匹配是会出现错误的,因为一头必须同时匹配食物和饮料。正确的解法就是网络流啦,建图思路比较巧妙,建议没太弄懂的好哈理解。但是一开始是这样建图源点-food-牛-drink-汇点,这样虽然满足每份food和drink只能给一头牛吃,但是没法解决每头牛只能吃一份的问题。如果是这样,源点-food-牛-牛-drink-汇点,将牛拆成两个点,里面的边权值全为1.用效率不是很高的Ek算法就能解决。所以最重要的建图过程。也是网络流的难点。

POJ 1087

题意:

给m个需要充电的装置,每个装置必须有对应的插座类型,现在只有n种插座,且每种插座只有一个,一个插座只能对应一个装置,现在有k种适配器u v ,可以使得类型为u的插座可以转换为类型为v的插座,且适配器之间可以嵌套使用,每种类型适配器数量无限,求最多能有多少充电装置成功充电?

思路:

二分匹配肯定是可以做的啦,不过需要加上k种电源适配器对应的边。网络流的做法呢,建图为源点 -> 充电装置 -> 插座 -> 汇点,因为充电装置只有一个,所以源点 -> 充电装置 边流量为1 ,而每种插座只能用一次,所以充电装置 -> 插座 边流量为1 ,而插座类型可以通过适配器等效的使用,所以由于加进适配器而增加的插座之间的边流量因为INF,但是注意一点就是插座 -> 汇点的建边,只能由那n个已有的插座向汇点建边,且流量为1。。。建图过程一定要理解。

POJ 2195

题意:

给定一个矩阵,知道人的位置和家的位置,每个家只能容纳一个人,且每个人到达一个家需要花费,求容纳最多人的最小花费?

思路:

比价明显的最小费用最大流,建图就是源点 -> 人 -> 家 -> 汇点,源点 -> 人,家 -> 汇点边的容量就是1,费用为0,而人 -> 家边的容量也是1,费用就是上面提到的花费了,然后跑一发最小费用流就行了!模板,get!

struct edge
{
    int to,next,cap,flow,cost;
}e[M];

int uN,head[N],tot;
int pre[N],dis[N];
bool vis[N];
void init()
{
    memset(head,-1,sizeof(head));
    tot=0;
}
void addedge(int u,int v,int cap,int cost)
{
    e[tot].to=v , e[tot].cap=cap , e[tot].cost=cost , e[tot].flow=0;
    e[tot].next=head[u] , head[u]=tot++;
    e[tot].to=u , e[tot].cap=0 , e[tot].cost=-cost , e[tot].flow=0;
    e[tot].next=head[v] , head[v]=tot++;
}
bool spfa(int s,int t)
{
    queue<int> q;
    for(int i=0 ; i<uN ;i++)
    {
        dis[i]=INF , vis[i]=0 , pre[i]=-1;
    }
    dis[s] = 0;
    vis[s] = 1;
    q.push(s);

    while(!q.empty())
    {
        int u=q.front(); q.pop();
        vis[u] = 0;
        for(int i = head[u]; i!=-1 ;i = e[i].next)
        {
            int v = e[i].to;
            if(e[i].cap > e[i].flow && dis[v] > dis[u] + e[i].cost){
                dis[v] = dis[u] + e[i].cost;
                pre[v] = i;
                if(!vis[v]) {
                    vis[v] = 1;
                    q.push(v);
                }
            }
        }
    }
    return pre[t] != -1;
}
int MincostMaxflow(int s,int t,int &cost)
{
    int flow = 0;
    cost = 0;
    while(spfa(s,t))
    {
        int Min_f = INF;
        for(int i = pre[t];i != -1 ;i = pre[e[i^1].to])
        {
            if(Min_f> e[i].cap-e[i].flow)
                Min_f = e[i].cap-e[i].flow;
        }
        for(int i = pre[t];i != -1 ;i = pre[e[i^1].to])
        {
            e[i].flow += Min_f;
            e[i^1].flow -= Min_f;
            cost += Min_f*e[i].cost;
        }
        flow += Min_f;
    }
    return flow;
}

POJ 2516

题意:

有n个店主,m个储存库,有k种物品,已知每个储存库k种物品的库存,以及每个店主k种物品的需求,和物品-店主-存储库一一对应的花费,求满足所有店主需求的情况下最小的花费是多少?

思路:

首先呢,这k种物品是独立互不影响的,所以我们单独考虑一种类型的物品,建图为源点->a储存库->b店主->c汇点。
a:cap=cost=0
b:cap=cost=
c:cap=cost=0
其实中间这条边b的流量是任意的(当然不能小于 min() ),只要正确的确定连接源点,汇点的边的流量就行了。

POJ 1459,HDU 4280,HDU 4292

思路:

这三个题之所以放在一起,是因为都是比较裸的最大流,而且要使用高效模板哦!HDU 4292跟POJ 3281是一样的题,只不过数据范围扩大到了800的样子,所以EK算法就会T了,附上高效模板-非递归形式的dicnic,代码量比较大,T_T!

const int N=810,M=5+1e6,MOD=7+1e9;
struct Node
{
    int from,to,next;
    int cap;
}edge[M];
int tol;

int dep[N];//dep为点的层次
int head[N];

void init()
{
    tol=0;
    memset(head,-1,sizeof(head));
}
void addedge(int u,int v,int w)//第一条变下标必须为偶数
{
    edge[tol].from=u;
    edge[tol].to=v;
    edge[tol].cap=w;//注意正向边流量值
    edge[tol].next=head[u];
    head[u]=tol++;

    edge[tol].from=v;
    edge[tol].to=u;
    edge[tol].cap=0;//注意反向边的流量值
    edge[tol].next=head[v];
    head[v]=tol++;
}

int BFS(int start,int end)
{
    int que[N];
    int front,rear;
    front=rear=0;
    memset(dep,-1,sizeof(dep));
    que[rear++]=start;
    dep[start]=0;
    while(front!=rear)
    {
        int u=que[front++];
        if(front==N)front=0;
        for(int i=head[u];i!=-1;i=edge[i].next)
        {
            int v=edge[i].to;
            if(edge[i].cap>0&&dep[v]==-1)
            {
                dep[v]=dep[u]+1;
                que[rear++]=v;
                if(rear>=N)rear=0;
                if(v==end)return 1;
            }
        }
    }
    return 0;
}
int dinic(int start,int end)
{
    int res=0;
    int top;
    int stack[N];//stack为栈,存储当前增广路
    int cur[N];//存储当前点的后继
    while(BFS(start,end))
    {
        memcpy(cur,head,sizeof(head));
        int u=start;
        top=0;
        while(1)
        {
            if(u==end)
            {
                int min=INF;
                int loc;
                for(int i=0;i<top;i++)
                  if(min>edge[stack[i]].cap)
                  {
                      min=edge[stack[i]].cap;
                      loc=i;
                  }
                for(int i=0;i<top;i++)
                {
                    edge[stack[i]].cap-=min;
                    edge[stack[i]^1].cap+=min;
                }
                res+=min;
                top=loc;
                u=edge[stack[top]].from;
            }
            for(int i=cur[u];i!=-1;cur[u]=i=edge[i].next)
              if(edge[i].cap!=0&&dep[u]+1==dep[edge[i].to])
                 break;
            if(cur[u]!=-1)
            {
                stack[top++]=cur[u];
                u=edge[cur[u]].to;
            }
            else
            {
                if(top==0)break;
                dep[u]=-1;
                u=edge[stack[--top]].from;
            }
        }
    }
    return res;
}

URAL 1774

题意:

一个理发师要给N个人服务,每个人要求只在时刻[s,t]内被服务两次,理发师同一时刻可以同时给k个人服务,问能否服务这N个人,可以的话,输出一种可行的方案。

思路:

艹,真正比赛的时候遇到网络流就跪了,这个锅是我的,比赛的时候就已经想的是以每个时刻也当做一个点,由n个人到其所对应的时间去建边,但是一个人要被服务两次,也就是这个人需要去选择两个不同的时刻去接受服务,就是这个地方一直没有想清楚,最后时刻也确实一直没有静下心来好好思考这个,所以。。。背锅了。。。。
其实只需要把流入每个人的流量改为2就行啦,真是傻逼,啊啊啊!!
最后就是所有时刻到汇点的流量为k,跑一发最大流就行了,E-K居然也能跑2000个点,日了狗了。。。。。。

SGU 326 Perspective

题意:

给定N个球队,已知每个球队现有的得分,还有每个球队还有多少比赛要打,还有这N个球队之间相互的比赛场数,问最终是否有可能球队1的得分最多或者并列最多?

思路:

*首先去贪心一下,得到球队1最多能得到的分数 Max ,很自然的是我们可以把两个队之间的比赛当作点,如果球队 i j 之间有 val 场比赛,那么 val 这个值可以分配给 i j ,但是为了满足条件(即球队1的得分最多或者并列最多),我们必须通过合理的分配使得另外 n1 个球队里的最大值最小化,那么如何去做呢?想啊,想啊,想啊,就。。一直想不出来。。。

*那么我们不妨反过来考虑,也就是建边由:球队->比赛->汇点,而这些边的流量值为 val ,最后我们只需判断它是否满流即可(即 Maxflow=val ),由源点到比赛的边的流量为 Maxr[i] ,也就是这支球队最多只能胜利的场数,一些细节稍微注意下就OK了!

*还是属于比较基础的题,这个题的启发就是正向建边不行的话,我们可以尝试反向去建边!还有就是这种最小化,最大值的问题。

以下为最小割相关例题


UVA 10480 Sabotage

题意:

给你一个简单连通的带权无向图,可以删除一些边使得点 S 和点 T 属于两个不相交的连通块,问最小花费为?所删除的边有哪些?

思路:

首先呢,得明白最小割的概念,这样的话,容易知道这就是一个裸的最小割模型,最小割=最大流,所以只需求一下最大流就可得到最小的花费,但是如何求这些割边呢?跑完最大流后,在残余网络上,用f[i]数组表示由 S 流向 i 的流量,初始化为f[S]=INF,其它为0,那么按照寻找增广路的过程,如果最终f[i]为0,是不是就说明了他是属于终点那一部分,
否则,说明流量还可以继续流向i点,毫无疑问他就属于起点那一部分了!
这里就学习到了一种寻找最小割下割边的方法,注意在不同模型下灵活使用

拓展:

ZOJ 3792 Romantic Value
题目意思一样,但是多了一条件,就是在保证最大流的情况下,还要求割边的数量尽可能的少,转化思路非常巧妙,在求最大流的时候就顺便求出了割边的数量,方法就是把边权 val 转化为 val1000+1
大叫三声,好!!!好!!!好!!!

不断更新中。。。。。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值