最大流-最小割定理&poj3469 Dual Core CPU

本文介绍了最大流问题中的最小割概念及其求解方法,并通过一个具体例子展示了如何利用最大流等于最小割的原理来解决实际问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

对于最大流问题中,经常会涉及到一个叫做最小割的部分,那么,首先先来看看最小割是什么。

在一个图G(V,E)中,通过去掉一些边的方式,使得节点u与节点v不连通,这就是节点u和节点v的一个割。

在G之中,有一些路径联通s,t,如果去掉一些边使s,t不连通,那么这个割的办法就是一个s-t割。

每一个割的容量就是这个割集中所有边的容量之和。

讲完这些基础理论,自然容易猜到最小割就是在图G中,容量最小的一个s-t割。

那么要怎么求最小割呢?这里有一个定理:最大流等于最小割。

不难发现,在求最大流的过程中,必然有增广这个过程,以Ek算法执行的方式为例,找到一条s-t路径,其中残量最少的就是这一次所增加的流量,这一条边也相应的在残量网络中去掉,多次执行,假设只去掉残量最少的边,到最后,s和t在残量网络中必然是不连通的,反之,必然能够继续增广,这时候被去掉的边就是最小割的割集,最大流就是最小割。

那么怎么证明它是一定正确的呢?

简要证明:

求最大流时,流量不会停滞在某一个节点中,而是直接从s到t走下去,这样走下去,走过的边的流量都是一样的,这时候转化为最小割问题,就相当于是那么有残量的边都还保留着,没有去掉多余的边。这样求下去,最终一定能够求出最大流,相应地也求出了最小割。

所以,这个定理就是正确的。

那么现在来看一道题目:

题意:

有机器A和B,N个任务,每一个任务在A上的花费和在B上的花费是不一样的。如果任务x和任务y不在同一台机器上工作,那么还要额外花费,求最少花费。

思路:

可以很快想到,这是一道求最小割的题目,首先要建图。

源点表示机器A,汇点表示机器B,源点向每一个节点连接相应的花费,每一个节点向汇点连接相应的花费。

要在同一台机器上工作的两个节点再另外连相应的花费,求一次最小割,也就是最大流即可。

这里有一个要注意的地方,两个任务之间的边应该是无向的,而不是有向的,不然会导致错误。

同时注意到一点,题目的数据量十分大,使用Dinic理论上最坏情况会跑O(80000000000000)的时间,虽然有5秒的限时,但还是肯定会超时嘛……但是实际上,并不会跑这么久都完成不了……


事实上跑得还是很快的,并没有超时,尽管也是临界了……但是如果使用迭代以及不使用STL库的vector来储存,和用queue来做BFS,应该会跑3200MS左右,但是为了方便并没有用,万一用了数组开得不够大呢……

代码:

#include <queue>
#include <vector>
#include <cstdio>
#include <cstring>
#include <iostream>

using namespace std;

const int MAXN = 20000 + 10,
          INF = 0x3f3f3f3f;

const int s = 0,
          t = 20001;

struct Edge {
    int from, to, cap, flow;
};

struct Dinic {
	int n, m, s, t;
	vector < Edge > edges;
	vector < int > G[MAXN];
	bool vis[MAXN];
	int d[MAXN];
	int cur[MAXN];
	
	void AddEdge(int from, int to, int cap) {
		edges.push_back((Edge) {
			from, to, cap, 0
		});
		
		if(from == s || to == t) { // 如果不判断是否是两个点之间连,然后直接将边改成无向,就超时和爆空间了……
			edges.push_back((Edge) {
				to, from, 0, 0
			});
		} else {
			edges.push_back((Edge) {
				to, from, cap, 0
			});
		}
		m = edges.size();
		G[from].push_back(m - 2);
		G[to].push_back(m - 1);
	}
	
	bool BFS() {
		memset(vis, 0, sizeof vis);
		queue < int > Q;
		Q.push(s);
		d[s] = 1;
		vis[s] = 1;
		
		while(!Q.empty()) {
			int x = Q.front();
			Q.pop();
			
			for(int i = 0; i < G[x].size(); ++i) {
				Edge& e = edges[G[x][i]];
				
				if(!vis[e.to] && e.cap > e.flow) {
					vis[e.to] = 1;
					d[e.to] = d[x] + 1;
					Q.push(e.to);
				}
			}
		}
		
		return vis[t];
	}
	
	int DFS(int x, int a) {
		if(x == t || a == 0) {
			return a;
		}
		
		int flow = 0, f;
		for(int& i = cur[x]; i < G[x].size(); ++i) {
			Edge& e = edges[G[x][i]];
			
			if(d[e.to] == d[x] + 1 && (f = DFS(e.to, min(a, e.cap - e.flow))) > 0) {
				e.flow += f;
				edges[G[x][i] ^ 1].flow -= f;
				flow += f;
				a -= f;
				
				if(a == 0) {
					break;
				}
			}
		}
	
		return flow;
	}
	
	int Maxflow(int s, int t) {
		this -> s = s;
		this -> t = t;
		
		int flow = 0;
		
		while(BFS()) {
			memset(cur, 0, sizeof cur);
			flow += DFS(s, INF);
		}
		return flow;
	}
} dinic;

int n, m;

int main(void) {
    scanf("%d%d", &n, &m);

    for(int i = 1; i <= n; ++i) {
        int a, b;
        scanf("%d%d", &a, &b);
        dinic.AddEdge(s, i, a); // 源点与汇点都和节点之间连边
        dinic.AddEdge(i, t, b);
    }

    for(int i = 0; i < m; ++i) {
        int a, b, cap;
        scanf("%d%d%d", &a, &b, &cap);
        dinic.AddEdge(a, b, cap); // 节点之间连边
    }

    printf("%d\n", dinic.Maxflow(s, t)); // 求最小割
    
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值