您的位置:首页 > 其它

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

2016-07-30 20:25 288 查看
对于最大流问题中,经常会涉及到一个叫做最小割的部分,那么,首先先来看看最小割是什么。

在一个图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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: