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算法
  1. 算法思路:

    通过多次bfs每次bfs找到一条增广路,然后更新残留网络,来寻找最大流。
  2. 算法复杂度:这个是实现起来最慢的一个,对于这个题用时 79MS
  3. 算法模板:
#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];
}
}
};
  1. 这个题的代码:
#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算法
  1. 算法思路:

    一次bfs一次dfs,每次bfs给每个点一个层数值(当前点到源点的距离),然后dfs通过层数的限制一次可以更新多条增广路,来寻找最大流。
  2. 算法复杂度:这个是实现起来稍微快一点的一个,一般可以解决大多数问题,复杂度是\(O(n^2m)\),对于这个题用时 32MS
  3. 算法模板:
#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算法

  1. 算法思路:

    只一次bfs和dfs动态更新标号值,bfs给每个点一个层数值(当前点到汇点的距离),然后dfs通过层数的限制一次可以更新多条增广路,来寻找最大流。这里注意,dfs过程中动态更新标号值,如果当前的这个点到汇点的边的残余流量值为0说明这个点不能再通过这个边到达汇点,所以要将这个点的标号标记为它链接的孩子的节点中标号最小的那个的标号值+1,具体的可以画个图来看一下。

    这个算法之所以快,是因为可以加一个gap优化,即设一个数组保存标号为i的节点个数为d[i]这样在d[i]出现在\(0<i<n\)的时候有d[i]==0则这个时候直接结束即可,说明从汇点已经不能到达源点了。已经没有增广路了
  2. 算法复杂度:这个是实现起来较快的一个,一般可以解决大多数问题,复杂度是\(O(n^2m)\),如果用优先队列存图,复杂度可以到达\(O(n^2\sqrt{m})\),对于这个题用时 0MS
  3. 算法模板:
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为边的数量
  1. 这个题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;
}

poj_3281Dining(网络流+拆点)的更多相关文章

  1. ACM/ICPC 之 网络流-拆点构图(POJ2391)

    需要直接到达,因此源点经过三条边后必须要达到汇点,但为了保证网络流的正确性(路径可反悔),因此不可限制层次网络的最高层次为3,最好的方法既是让所有点拆分成两个点,一个点从汇点进入,一个点通向汇点,任意 ...

  2. hiho# 1394最小路径覆盖 网络流拆点

    题目传送门 思路: 观察到路径上除了终点起点以外的每个点出度和入度都为1,和网络流的拆点很像,所以就把每个点都拆成两个点,若存在一条路径$(u,v)$,则建一条$(u,v+n,1)$的边,然后求出最大 ...

  3. poj 3281 Dining(网络流+拆点)

    Dining Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 20052   Accepted: 8915 Descripti ...

  4. POJ2391 Ombrophobic Bovines 网络流拆点+二分+floyed

    题目链接: id=2391">poj2391 题意: 有n块草地,每块草地上有一定数量的奶牛和一个雨棚,并给出了每一个雨棚的容(牛)量. 有m条路径连接这些草地  ,这些路径是双向的, ...

  5. ACM Computer Factory POJ - 3436 网络流拆点+路径还原

    http://poj.org/problem?id=3436 每台电脑有$p$个组成部分,有$n$个工厂加工电脑. 每个工厂对于进入工厂的半成品的每个组成部分都有要求,由$p$个数字描述,0代表这个部 ...

  6. Food HDU - 4292 网络流 拆点建图

    http://acm.hdu.edu.cn/showproblem.php?pid=4292 给一些人想要的食物和饮料,和你拥有的数量,问最多多少人可以同时获得一份食物和一份饮料 写的时候一共用了2种 ...

  7. poj 3281(网络流+拆点)

    题目链接:http://poj.org/problem?id=3281 思路:设一个超级源点和一个超级汇点,源点与食物相连,饮料与汇点相连,然后就是对牛进行拆点,一边喜欢的食物相连,一边与喜欢的饮料相 ...

  8. POJ 3281 Dining(网络流拆点)

    [题目链接] http://poj.org/problem?id=3281 [题目大意] 给出一些食物,一些饮料,每头牛只喜欢一些种类的食物和饮料, 但是每头牛最多只能得到一种饮料和食物,问可以最多满 ...

  9. hdu 4289 网络流拆点,类似最小割(可做模板)邻接矩阵实现

    Control Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Sub ...

随机推荐

  1. MySQL 如何存储长度较大的varchar与blob

    本文同时发表在https://github.com/zhangyachen/zhangyachen.github.io/issues/96 最近,在工作中遇到了MySQL中如何存储长度较长的字段类型问 ...

  2. Swift学习第二天--面向对象

    //: Playground - noun: a place where people can play import UIKit var str = "Hello, playground& ...

  3. ES6 数组的扩展

    1. Array.from() Array.from()将类数组(array-like)对象与可遍历的对象转化为数组并返回. 下面是一个类数组 let arr = { '0':'a', '1':'b' ...

  4. 关于HTTP协议头域详解

    HTTP1.1 请求头:消息头  Accept:text/html,image/*  告诉服务器,客户机支持的数据类型 Accept-Charset:ISO-8859-1  告诉服务器,客户机采用的编 ...

  5. ubuntu 安装 pythonenv

    This will get you going with the latest version of pyenv and make it easy to fork and contribute any ...

  6. struts2 使用filter解决中文乱码问题

    1.编写fliter的代码 import java.io.IOException;import javax.servlet.Filter;import javax.servlet.FilterChai ...

  7. 前端生成验证码图片utils

    <%@ page language="java" contentType="text/html; charset=UTF-8"     pageEncod ...

  8. Python并发实践_02_通过yield实现协程

    python中实现并发的方式有很多种,通过多进程并发可以真正利用多核资源,而多线程并发则实现了进程内资源的共享,然而Python中由于GIL的存在,多线程是没有办法真正实现多核资源的. 对于计算密集型 ...

  9. dubbo源码—dubbo简介

    dubbo是一个RPC框架,应用方像使用本地service一样使用dubbo service.dubbo体系架构 上图中的角色: 最重要的是consumer.registry和provider con ...

  10. Ubuntu16.04 编译 OpenJDK7

    <深入理解Java虚拟机>第二版第一章实践 准备 Mercurial sudo apt-get install mercurial OpenJDK7 hg clone http://hg. ...