poj_3281Dining(网络流+拆点)
poj_3281Dining(网络流+拆点)
标签: 网络流
题意:
一头牛只吃特定的几种食物和特定的几种饮料,John手里每种食物和饮料都只有一个,问最多能够满足几头牛的需求(水和食物都必须和他们的胃口)。
题解:
网络流
建图:从源点向每个食物连一条边,容量为1,
将牛拆成两个点牛,牛',中间连边,容量为1
从食物向牛连边,容量为1
连牛'和饮料,容量为1
连饮料和汇点,容量为1
网络流三种算法的理解和代码实现以及相应的模板
先介绍一个定理:
最大流最小割定理:
割:去掉某几条边使得源点和汇点不再联通,那么这几条边就叫一个割,这几条边的边权和最小的就叫做最小割。
一个图的最大流就是这个图对应的最小割,可以看做是一个沙漏,最大流是要求这个沙漏每个时刻最大的流量,那就相当于是求沙漏最细的地方的流量。而最小割就相当于是用一个刀子将这个沙漏一分为二,然后找横截切面最小的就是这个沙漏最细的地方。
再介绍一些网络流的一些基本性质
流网络G的流(flow)是一个实值函数 f :V ×V → R,且满足下列三个性质
(1) 容量限制:对于∀u,v∈V ,要求 f (u,v) ≤ c(u,v)。
(2) 反对称性:对于∀u,v∈V ,要求 f (u,v) = −f (v,u)。
(3) 流守恒性:对于∀u,v∈V −{s,t},要求∑f (u,v) =0。
f(u,v)即为u到v的流。流 f =∑f(S,v);
最大流顾名思义就是使流f最大,也就是水龙头里能放出的水要最多。
- EdmondsKarp算法
- 算法思路:
通过多次bfs每次bfs找到一条增广路,然后更新残留网络,来寻找最大流。 - 算法复杂度:这个是实现起来最慢的一个,对于这个题用时 79MS
- 算法模板:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
const int maxn = 1008;
const int INF = 111111111;
struct Edge{
int from,to,cap,flow;
Edge(int u,int v,int c,int f):from(u),to(v),cap(c),flow(f){}
};
int s,t;//源点。汇点
struct EdmondsKarp{
int n,m;
vector<Edge>edge; //边数的两倍
vector<int>G[maxn]; //邻接表,G[i][j]表示i的第j条边在e数组中的序号
int a[maxn]; //当起点到i的可改进量
int p[maxn]; //最短路树上p的入弧编号
void init(int n){
for(int i=0;i<=n;i++) G[i].clear();
edge.clear();
}
void AddEdge(int from,int to,int cap){
edge.push_back(Edge(from,to,cap,0));
edge.push_back(Edge(to,from,0,0)); //反向弧
m=edge.size();
G[from].push_back(m-2);
G[to].push_back(m-1);
}
int Maxflow(int s,int t){
int flow=0;
for(;;){
memset(a,0,sizeof(a));
queue<int>q;
while(!q.empty()) q.pop();
q.push(s);
a[s]=INF;
while(!q.empty()){
int x=q.front();q.pop();
for(int i=0;i<G[x].size();i++){
Edge& e=edge[G[x][i]];
if(!a[e.to]&&e.cap>e.flow){
p[e.to]=G[x][i];
a[e.to]=min(a[x],e.cap-e.flow);
q.push(e.to);
}
}
if(a[t]) break;
}
if(!a[t]) return flow;
for(int u=t;u!=s;u=edge[p[u]].from){
edge[p[u]].flow+=a[t];
edge[p[u]^1].flow-=a[t];
}
flow+=a[t];
}
}
};
- 这个题的代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
const int maxn = 1008;
const int INF = 111111111;
struct Edge{
int from,to,cap,flow;
Edge(int u,int v,int c,int f):from(u),to(v),cap(c),flow(f){}
};
int s,t;
struct EdmondsKarp{
int n,m;
vector<Edge>edge; //边数的两倍
vector<int>G[maxn]; //邻接表,G[i][j]表示i的第j条边在e数组中的序号
int a[maxn]; //当起点到i的可改进量
int p[maxn]; //最短路树上p的入弧编号
void init(int n){
for(int i=0;i<=n;i++) G[i].clear();
edge.clear();
}
void AddEdge(int from,int to,int cap){
edge.push_back(Edge(from,to,cap,0));
edge.push_back(Edge(to,from,0,0)); //反向弧
m=edge.size();
G[from].push_back(m-2);
G[to].push_back(m-1);
}
int Maxflow(int s,int t){
int flow=0;
for(;;){
memset(a,0,sizeof(a));
queue<int>q;
while(!q.empty()) q.pop();
q.push(s);
a[s]=INF;
while(!q.empty()){
int x=q.front();q.pop();
for(int i=0;i<G[x].size();i++){
Edge& e=edge[G[x][i]];
if(!a[e.to]&&e.cap>e.flow){
p[e.to]=G[x][i];
a[e.to]=min(a[x],e.cap-e.flow);
q.push(e.to);
}
}
if(a[t]) break;
}
if(!a[t]) return flow;
for(int u=t;u!=s;u=edge[p[u]].from){
edge[p[u]].flow+=a[t];
edge[p[u]^1].flow-=a[t];
}
flow+=a[t];
}
}
};
int main()
{
int N,F,D;
s = 0;
while(~scanf("%d%d%d",&N,&F,&D))
{
EdmondsKarp EK;
t = N*2+F+D+1;
EK.init(t+1);
int f,d;
for(int i = 1; i <= F; i++){//源点向各种食物连边
EK.AddEdge(s,i,1);
}
int tm;
for(int i = 1; i <= N; i++){
scanf("%d%d",&f,&d);
for(int j = 1; j <= f; j++){
scanf("%d",&tm);
EK.AddEdge(tm,F+i*2-1,1);//食物向喜欢它的牛连边
}
EK.AddEdge(F+i*2-1,F+i*2,1);//牛拆点
for(int j = 1; j <= d; j++){
scanf("%d",&tm);
EK.AddEdge(F+i*2,2*N+F+tm,1);//牛向喜欢的饮料连边
}
}
for(int i = 1; i <= D; i++){
EK.AddEdge(2*N+F+i,t,1);//所有饮料向汇点连边
}
//puts("haha");
int ans = EK.Maxflow(0,t);
printf("%d\n",ans);
}
return 0;
}
- Dinic算法
- 算法思路:
一次bfs一次dfs,每次bfs给每个点一个层数值(当前点到源点的距离),然后dfs通过层数的限制一次可以更新多条增广路,来寻找最大流。 - 算法复杂度:这个是实现起来稍微快一点的一个,一般可以解决大多数问题,复杂度是\(O(n^2m)\),对于这个题用时 32MS
- 算法模板:
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
const int maxn = 500 + 5;
const int INF = 100000000;
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 init(int n)
{
this->n = n;
for(int i=0; i<=n; ++i) G[i].clear();
edges.clear();
}
void AddEdge(int from, int to, int cap)
{
edges.push_back((Edge){from, to, cap, 0});
edges.push_back((Edge){to, from, 0, 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);
vis[s] = 1;
d[s] = 0;
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[x] + 1 == d[e.to] && (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;
}
}DD;
/*初始化源点汇点,点数
DD.s =
DD.t =
DD.n =
DD.init(DD.n);
//加边操作
DD.AddEdge(DD.s,i,1);
//计算结果
int ans = DD.Maxflow(0,DD.t);
4.这个题ac代码:
//ac了的dinic模板
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
const int maxn = 500 + 5;
const int INF = 100000000;
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 init(int n)
{
this->n = n;
for(int i=0; i<=n; ++i) G[i].clear();
edges.clear();
}
void AddEdge(int from, int to, int cap)
{
edges.push_back((Edge){from, to, cap, 0});
edges.push_back((Edge){to, from, 0, 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);
vis[s] = 1;
d[s] = 0;
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[x] + 1 == d[e.to] && (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;
}
}DD;
int main()
{
int N,F,D;
DD.s = 0;
while(~scanf("%d%d%d",&N,&F,&D))
{
DD.t = N*2+F+D+1;
DD.n = DD.t+1;
DD.init(DD.n);
int f,d;
for(int i = 1; i <= F; i++){//源点向各种食物连边
DD.AddEdge(DD.s,i,1);
}
int tm;
for(int i = 1; i <= N; i++){
scanf("%d%d",&f,&d);
for(int j = 1; j <= f; j++){
scanf("%d",&tm);
DD.AddEdge(tm,F+i*2-1,1);//食物向喜欢它的牛连边
}
DD.AddEdge(F+i*2-1,F+i*2,1);//牛拆点
for(int j = 1; j <= d; j++){
scanf("%d",&tm);
DD.AddEdge(F+i*2,2*N+F+tm,1);//牛向喜欢的饮料连边
}
}
for(int i = 1; i <= D; i++){
DD.AddEdge(2*N+F+i,DD.t,1);//所有饮料向汇点连边
}
//puts("haha");
int ans = DD.Maxflow(0,DD.t);
printf("%d\n",ans);
}
return 0;
}
*ISAP算法
- 算法思路:
只一次bfs和dfs动态更新标号值,bfs给每个点一个层数值(当前点到汇点的距离),然后dfs通过层数的限制一次可以更新多条增广路,来寻找最大流。这里注意,dfs过程中动态更新标号值,如果当前的这个点到汇点的边的残余流量值为0说明这个点不能再通过这个边到达汇点,所以要将这个点的标号标记为它链接的孩子的节点中标号最小的那个的标号值+1,具体的可以画个图来看一下。
这个算法之所以快,是因为可以加一个gap优化,即设一个数组保存标号为i的节点个数为d[i]这样在d[i]出现在\(0<i<n\)的时候有d[i]==0则这个时候直接结束即可,说明从汇点已经不能到达源点了。已经没有增广路了 - 算法复杂度:这个是实现起来较快的一个,一般可以解决大多数问题,复杂度是\(O(n^2m)\),如果用优先队列存图,复杂度可以到达\(O(n^2\sqrt{m})\),对于这个题用时 0MS
- 算法模板:
const int inf = 0x3fffffff;
template <int N, int M>
struct Isap
{
int top;
int d[N], pre[N], cur[N], gap[N];
struct Vertex{
int head;
} V[N];
struct Edge{
int v, next;
int c, f;
} E[M];
void init(){
memset(V, -1, sizeof(V));
top = 0;
}
void add_edge(int u, int v, int c){
E[top].v = v;
E[top].c = c;
E[top].f = 0;
E[top].next = V[u].head;
V[u].head = top++;
}
void add(int u,int v, int c){
add_edge(u, v, c);
add_edge(v, u, 0);
}
void set_d(int t){
queue<int> Q;
memset(d, -1, sizeof(d));
memset(gap, 0, sizeof(gap));
d[t] = 0;
Q.push(t);
while(!Q.empty()) {
int v = Q.front(); Q.pop();
++gap[d[v]];
for(int i = V[v].head; ~i; i = E[i].next) {
int u = E[i].v;
if(d[u] == -1) {
d[u] = d[v] + 1;
Q.push(u);
}
}
}
}
int sap(int s, int t, int num) {
set_d(t);
int ans = 0, u = s;
int flow = inf;
memcpy(cur, V, sizeof(V));
while(d[s] < num) {
int &i = cur[u];
for(; ~i; i = E[i].next) {
int v = E[i].v;
if(E[i].c > E[i].f && d[u] == d[v] + 1) {
u = v;
pre[v] = i;
flow = min(flow, E[i].c - E[i].f);
if(u == t) {
while(u != s) {
int j = pre[u];
E[j].f += flow;
E[j^1].f -= flow;
u = E[j^1].v;
}
ans += flow;
flow = inf;
}
break;
}
}
if(i == -1) {
if(--gap[d[u]] == 0)
break;
int dmin = num - 1;
cur[u] = V[u].head;
for(int j = V[u].head; ~j; j = E[j].next)
if(E[j].c > E[j].f)
dmin = min(dmin, d[E[j].v]);
d[u] = dmin + 1;
++gap[d[u]];
if(u != s)
u = E[pre[u] ^ 1].v;
}
}
return ans;
}
};
Isap<1000, 1000000> Sap;
调用方式:
Sap.init(); //建边前调用
Sap.add(u, v, c); //在u->v之间建一条容量为c的边
Sap.sap(s, t, num); //s为源点,t为汇点,num为边的数量
- 这个题AC代码:
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
const int inf = 0x3fffffff;
template <int N, int M>
struct Isap
{
int top;
int d[N], pre[N], cur[N], gap[N];
struct Vertex{
int head;
} V[N];
struct Edge{
int v, next;
int c, f;
} E[M];
void init(){
memset(V, -1, sizeof(V));
top = 0;
}
void add_edge(int u, int v, int c){
E[top].v = v;
E[top].c = c;
E[top].f = 0;
E[top].next = V[u].head;
V[u].head = top++;
}
void add(int u,int v, int c){
add_edge(u, v, c);
add_edge(v, u, 0);
}
void set_d(int t){
queue<int> Q;
memset(d, -1, sizeof(d));
memset(gap, 0, sizeof(gap));
d[t] = 0;
Q.push(t);
while(!Q.empty()) {
int v = Q.front(); Q.pop();
++gap[d[v]];
for(int i = V[v].head; ~i; i = E[i].next) {
int u = E[i].v;
if(d[u] == -1) {
d[u] = d[v] + 1;
Q.push(u);
}
}
}
}
int sap(int s, int t, int num) {
set_d(t);
int ans = 0, u = s;
int flow = inf;
memcpy(cur, V, sizeof(V));
while(d[s] < num) {
int &i = cur[u];
for(; ~i; i = E[i].next) {
int v = E[i].v;
if(E[i].c > E[i].f && d[u] == d[v] + 1) {
u = v;
pre[v] = i;
flow = min(flow, E[i].c - E[i].f);
if(u == t) {
while(u != s) {
int j = pre[u];
E[j].f += flow;
E[j^1].f -= flow;
u = E[j^1].v;
}
ans += flow;
flow = inf;
}
break;
}
}
if(i == -1) {
if(--gap[d[u]] == 0)
break;
int dmin = num - 1;
cur[u] = V[u].head;
for(int j = V[u].head; ~j; j = E[j].next)
if(E[j].c > E[j].f)
dmin = min(dmin, d[E[j].v]);
d[u] = dmin + 1;
++gap[d[u]];
if(u != s)
u = E[pre[u] ^ 1].v;
}
}
return ans;
}
};
Isap<1000, 1000000> Sap;
int main()
{
int N,F,D;
int s, t, num;
while(~scanf("%d%d%d",&N,&F,&D))
{
t = N*2+F+D+1;
s = 0;
num = t+1;
Sap.init();
int f,d;
for(int i = 1; i <= F; i++){//源点向各种食物连边
Sap.add(s,i,1);
}
int tm;
for(int i = 1; i <= N; i++){
scanf("%d%d",&f,&d);
for(int j = 1; j <= f; j++){
scanf("%d",&tm);
Sap.add(tm,F+i*2-1,1);//食物向喜欢它的牛连边
}
Sap.add(F+i*2-1,F+i*2,1);//牛拆点
for(int j = 1; j <= d; j++){
scanf("%d",&tm);
Sap.add(F+i*2,2*N+F+tm,1);//牛向喜欢的饮料连边
}
}
for(int i = 1; i <= D; i++){
Sap.add(2*N+F+i,t,1);//所有饮料向汇点连边
}
//puts("haha");
int ans = Sap.sap(s,t,num);
printf("%d\n",ans);
}
return 0;
}