最大流-最小割定理&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;
}
在一个图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;
}
相关文章推荐
- 【matlab学习】cell相关用法
- 03-即时通讯 环信集成
- 《算法导论》第一章_读书笔记
- STL学习笔记
- Wireshark入门与进阶系列四之过滤语法
- 02-即时通讯-XMPP 简单介绍
- 【整理】JS中getElementsByName()方法
- Linux环境下VI/VIM编辑文件时无权限保存的解决方法
- ASP.NET MVC开发:Web项目开发必备知识点
- C语言#define的用法
- 我每天都在github上做些什么
- HDU 5222 Exploration 混合图判断是否成环
- bash 常用操作
- codeforces-702A-Maximum Increase
- iOS-跟着标哥微博&学习导航头像缩放
- ubuntu软件卸载
- 网页屏幕上有只小虫在到处爬(代码)
- linux安装Telnet工具
- 习题3-9 子序列 UVa10340
- 动态规划之背包问题