照着刘汝佳的训练指南,网络流从开始学习到刷了10来题,共花了10天,其中不知道为什么有好几天的断档。。
网络流,目前我就用着dinic算法,一开始是用的EK算法,即普通的增广路算法。本来还打算搞下ISAP算法,不过听铭神说这两种算法他在做题时,一般ISAP能过的,Dinic一样过了。so,那就算了,暂时先放放,时间也不多。
————————————————————————————————————————————————
题目:
http://vjudge.net/contest/view.action?cid=48586#overview
上面的链接是我挂在虚拟OJ上的contest,都是从下面网络流题集中调出来一些的。
网络流的题集:http://blog.csdn.net/shahdza/article/details/7779537 此处很齐全。
体会:
网络流主要注重你的构图能力,一开始还以为最大流的算法会很难的,其实还可以接受。
感觉以后刷专题要写博客的话,还是A一题写一篇,到最后再整合。一次写10多题的题解,好累。。。
————————————————————————————————————————————————
A - PIGSpoj 1149
一开始不会,后来看了看题解,神一般的分析,代码就不敲了。
http://www.cnblogs.com/sleeper-qp/archive/2012/07/23/2605253.html
B - Drainage Ditches
模板题,不过会有重边,注意一下。
*我一开始用EK算法敲过了,不知为何,后来学会用Dinic算法敲就不对。后来还没找到错误,先mark一下。
EK算法(邻接矩阵):
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <queue>
#include <vector>
#include <iostream>
#define debug cout<<"??"<<endl;
using namespace std;
const int INF = 10000005;
const int SIZE = 205;
int n,m;
int a[SIZE],pre[SIZE];//a == vis;pre record its parent node
int flow[SIZE][SIZE];
int G[SIZE][SIZE];
int deal()
{
queue <int> q;
int f = 0;
for(;;)
{
memset(a,0,sizeof(a));
//memset(pre,0,sizoef(0));
a[1] = INF;
pre[1] = -1;
q.push(1);
while(!q.empty())
{
int u = q.front();
q.pop();
for(int i = 1;i <= m;i++)
{
if(!a[i] && G[u][i] > flow[u][i])
{
q.push(i);
a[i] = min(a[u],G[u][i] - flow[u][i]);
pre[i] = u;
}
}
}
if(a[m] == 0) break;
int t = m;
while(t != -1)
{
flow[pre[t]][t] += a[m];//正向
flow[t][pre[t]] -= a[m];//逆向
t = pre[t];
}
f += a[m];
}
return f;
}
int main()
{
while(scanf("%d%d",&n,&m) != EOF)
{
memset(G,0,sizeof(G)); //上限
memset(flow,0,sizeof(flow));//flow 为当前管道已经占用的容量
int u,v,cap;
for(int i = 0;i < n;i++)
{
scanf("%d%d%d",&u,&v,&cap);
G[u][v] += cap;
}
printf("%d\n",deal());
}
return 0;
}</span>
C - Power Network
题意描述有点不清晰,容易让人理解错误。不过后来直接看例子就懂了。
power station :供能
consumer:耗能
dispatcher:转发(普通点)
自己创建一个s点,连到每个power station上,容量为power station的供能能力。
创建一个t点,每个consumer连到t点,容量为consumer的消耗能力。
*自己连到自己的边,可以忽略掉
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <queue>
#include <iostream>
#include <cmath>
using namespace std;
#define debug cout<<"debug"<<endl;
const int MAXN = 110;
const int MAXEDGE = 21000;
const int INF = 0x7ffffff;
struct Edge
{
int to,cap,flow,next;
};
struct Dinic
{
int m, s, t;
Edge edge[MAXEDGE];
int head[MAXN], d[MAXN], cur[MAXN];
bool vis[MAXN];
Dinic(int s, int t)
{
this -> s = s,this -> t = t;
memset(head, -1, sizeof(head));
m = 0;
}
void AddEdge(int u,int v, int c)
{
edge[m] = (Edge){v, c, 0,head[u]};
head[u] = m++;
edge[m] = (Edge){u, 0, 0,head[v]};
head[v] = m++;
}
bool bfs()
{
memset(vis,false,sizeof(vis));
d[s] = 0;
vis[s] = true;
queue <int> q;
q.push(s);
while(!q.empty())
{
int u = q.front();
int next = head[u];
q.pop();
while(next != -1)
{
Edge &e = edge[next];
if(!vis[e.to] && e.cap > e.flow)
{
vis[e.to] = true;
q.push(e.to);
d[e.to] = d[u] + 1;
}
next = e.next;
}
}
return vis[t];
}
int dfs(int x, int a)
{
if(x == t || a == 0) return a;
int flow = 0, f;
int& next = cur[x];
while(next != -1)
{
Edge& e = edge[next];
if(d[e.to] == d[x] + 1 && (f = dfs(e.to, min(a, e.cap - e.flow))) > 0)
{
e.flow += f;
edge[next^1].flow -= f;
flow += f;
a -= f;
if(a == 0) break; //直到当前节点的最大残量若为0,则说明前面存在某条边已达上限
}
next = e.next;
}
return flow;
}
int getMaxFlow(int n)
{
int f = 0;
while(bfs())
{
for(int i = 0;i < n+2; i++)
cur[i] = head[i];
f += dfs(s, INF);
}
return f;
}
};
int main()
{
int n,np,nc,m;
while(scanf("%d %d %d %d",&n, &np, &nc, &m) != EOF)
{
int u, v, c;
int s = n, t = n + 1;
Dinic dinic(s, t);
for(int i = 0;i < m; i++)
{
scanf(" (%d,%d)%d",&u,&v,&c);
if(u == v) continue;
dinic.AddEdge(u,v,c);
}
for(int i = 0;i < np; i++)
{
scanf(" (%d)%d",&v,&c);
dinic.AddEdge(s, v, c);
}
for(int i = 0;i < nc; i++)
{
scanf(" (%d)%d",&u,&c);
dinic.AddEdge(u, t, c);
}
printf("%d\n",dinic.getMaxFlow(n));
}
return 0;
}</span>
D - Optimal Milking
不会= =, 直说了。后来看了题解,二分+floyd+最大流。。。主要对floyd以及二分不熟悉,因此暂时先搁着。
http://www.cppblog.com/abilitytao/archive/2010/11/06/132666.html
E - Dining
主要是构图。拆点,把牛拆成两个点。用容量为1的边相连(实际图中每条边的容量都是1)
构图方式:S —— 食物food——牛——牛——饮料drink——T
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <vector>
#include <queue>
#define debug cout<<"debug"<<endl;
using namespace std;
const int MAXPOINT = 505;
const int MAXEDGE = 300050;
const int INF = 1;
struct Edge
{
int to, cap, flow, next;
};
Edge edge[MAXEDGE];
struct Dinic
{
int n, m, s, t;
int cur[MAXPOINT], d[MAXPOINT];
bool vis[MAXPOINT];
int head[MAXPOINT];
Dinic(int n, int s, int t)
{
m = 0;
memset(head, -1, sizeof(head));
this->n = n, this->s = s,this->t = t;
}
void AddEdge(int u, int v, int cap)
{
edge[m] = (Edge){v, cap, 0, head[u]};
head[u] = m++;
edge[m] = (Edge){u, 0, 0,head[v]};
head[v] = m++;
}
int getMaxFlow()
{
int f = 0;
while(bfs())
{
for(int i = 0;i < n; i++)
cur[i] = head[i];
f += dfs(s, INF);
}
return f;
}
bool bfs()
{
memset(vis, false,sizeof(vis));
d[s] = 0, vis[s] = true;
queue <int> q;
q.push(s);
while(!q.empty())
{
int u = q.front();
q.pop();
int next = head[u];
while(next != -1)
{
Edge&e = edge[next];
if(!vis[e.to] && e.cap > e.flow)
{
vis[e.to] = true;
q.push(e.to);
d[e.to] = d[u] + 1;
}
next = e.next;
}
}
return vis[t];
}
int dfs(int u, int a)
{
if(u == t || a == 0) return a;
int flow = 0, f;
int& next = cur[u];
while(next != -1)
{
Edge& e = edge[next];
if(d[e.to] == d[u] + 1 && (f = dfs(e.to, min(a, e.cap - e.flow))) > 0)
{
e.flow += f;
edge[next^1].flow -= f;
a -= f;
flow += f;
if(a == 0) break;
}
next = e.next;
}
return flow;
}
void display(int n)
{
int next = head[n];
while(next != -1)
{
printf("%d ",edge[next].to);
next =edge[next].next;
}
puts("");
}
};
int main()
{
int n, f, d;
while(scanf("%d%d%d", &n, &f, &d) != EOF)
{
int s = 0, t = f+d+2*n+1;
Dinic dinic(t+1, s, t);
//s to food
for(int i = 1;i <= f; i++)
{
dinic.AddEdge(s, i, 1);
}
//cow1 to cow2
for(int i = 1;i <= n; i++)
{
dinic.AddEdge(f+i, f+n+i, 1);
}
int nf, nd, tmp;
for(int i = 1;i <= n; i++)
{
scanf("%d%d", &nf, &nd);
//food to cow1
for(int j = 0;j < nf; j++)
{
scanf("%d",&tmp);
dinic.AddEdge(tmp, f+i, 1);
}
//int num = n+f+1;
//cow2 to drink
for(int j = 0;j < nd; j++)
{
scanf("%d", &tmp);
dinic.AddEdge(f+n+i, f+2*n+tmp, 1);
}
}
//cow2 to t
for(int i = f+2*n+1;i <= f+2*n+d; i++)
{
dinic.AddEdge(i, t, 1);
}
printf("%d\n",dinic.getMaxFlow());
}
return 0;
}</span>
G - Flow Problem
原来还有一题是用EK算法的。直接套模板就好了。
H - Task Schedule
构图思想很重要。
S点连到每个任务,容量完成任务所需要的天数。
把每个任务连接到在天数完成的范围,容量均为1。例如要求第2天开始,第7天前要完成。那么,该任务就连到2,3,4...7天的结点。
每个天结点均连到T点,容量为机器的数量。
I - Leapin' Lizards
还是拆点。
1.首先处理哪些柱子可以直接跳出所在的room,则把这些柱子连到t点。
2.s点连到每根有蜥蜴的柱子上,容量为1。
3.柱子本身拆成两个点,连一条边。容量为:如果上面已经有蜥蜴了,则流量为原流量减 1,否则为原流量大小。
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <queue>
#include <vector>
#include <cmath>
#define cal(x1, y1, x2, y2) (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)
using namespace std;
const int MAXROW = 25;
const int MAXPOINT = 1005;
const int MAXEDGE = 500005;
char m1[MAXROW][MAXROW],m2[MAXROW][MAXROW];
const int INF = 10000000;
#define debug cout<<"debug"<<endl;
struct Edge
{
int to, cap, flow, next;
};
struct Point
{
int b1,b2;
};
Edge edge[MAXEDGE];
Point xb[MAXROW][MAXROW];
struct Dinic
{
int m, s, t; //m: the quantity of edge
int cur[MAXPOINT], head[MAXPOINT], d[MAXPOINT];
bool vis[MAXPOINT];
Dinic()
{
m = 0;
memset(head, -1, sizeof(head));
}
void init(int s, int t)
{
this -> s = s,this -> t = t;
}
void AddEdge(int u, int v, int cap)
{
edge[m] = (Edge){v, cap, 0, head[u]};
head[u] = m++;
edge[m] = (Edge){u, 0, 0,head[v]};
head[v] = m++;
}
bool bfs()
{
memset(vis, false,sizeof(vis));
queue <int> q;
q.push(s);
vis[s] = true, d[s] = 0;
while(!q.empty())
{
int u = q.front();
int next = head[u];
q.pop();
while(next != -1)
{
Edge& e = edge[next];
int v = e.to;
if(!vis[v] && e.cap > e.flow)
{
vis[v] = true;
q.push(v);
d[v] = d[u] + 1;
}
next = e.next;
}
}
return vis[t];
}
int dfs(int u, int a)
{
if(u == t || a == 0) return a;
int& next = cur[u];
int flow = 0, f;
while(next != -1)
{
Edge& e = edge[next];
int v = e.to;
if(d[v] == d[u] + 1 && (f = dfs(v, min(a, e.cap - e.flow))) > 0)
{
e.flow += f;
edge[next^1].flow -= f;
flow += f;
a -= f;
if(a == 0) break;
}
next = e.next;
}
return flow;
}
int getMaxFlow(int cot)
{
int f = 0;
while(bfs())
{
for(int i = 0;i < cot; i++)
cur[i] = head[i];
f += dfs(s, INF);
}
return f;
}
};
void input(int n)
{
for(int i = 0;i < n; i++)
{
scanf("%s", m1[i]);
}
for(int i = 0;i < n; i++)
{
scanf("%s", m2[i]);
}
}
int main()
{
int t;
scanf("%d",&t);
int cas = 0;
while(t--)
{
int n, dis;
scanf("%d%d",&n, &dis);
getchar();
input(n);
//build Graph
int lizard = 0;
int len = strlen(m1[0]);
int cot = 0; //
Dinic dinic;
for(int i = 0;i < n; i++) //³õʼ»¯±êºÅ
{
for(int j = 0;j < len; j++)
{
int num = m1[i][j] - '0';
if(num == 0) continue;
Point& e = xb[i][j];
e.b1 = cot++;
e.b2 = cot++;
if(m2[i][j] == 'L')
{
lizard++;
dinic.AddEdge(e.b2, e.b1, num-1);
//printf("%d %d %d",e.b2, e.b1, num-1);
}
else
{
dinic.AddEdge(e.b2, e.b1, num);
}
}
}
int s = cot, t = cot + 1;
dinic.init(s, t);
for(int i = 0;i < n; i++)
{
for(int j = 0;j < len; j++)
{
int num = m1[i][j] - '0';
if(num == 0) continue;
//point to t;
Point& e = xb[i][j];
if(i+dis > n-1 || j+dis > len-1 || i - dis < 0 || j - dis < 0)
{
dinic.AddEdge(e.b1, t, num);
//cout<<e.b1<<" "<<num<<endl;
}
//ponit to s;
if(m2[i][j] == 'L')
{
dinic.AddEdge(s, e.b1, 1);
}
//normal point to normal point
for(int t = max(0, i-dis); t < min(n, i + dis + 1); t++)
{
for(int p = max(0, j-dis); p < min(len, j + dis + 1); p++)
{
if((i == t && j == p) || m1[t][p] == '0') continue;
if(cal(i, j, t, p) > dis*dis) continue;
dinic.AddEdge(e.b1, xb[t][p].b2, INF);
}
}
}
}
int res = lizard - dinic.getMaxFlow(cot+2);
if(res == 0)
printf("Case #%d: no lizard was left behind.",++cas);
else if(res == 1)
printf("Case #%d: 1 lizard was left behind.",++cas);
else
printf("Case #%d: %d lizards were left behind.",++cas,res);
puts("");
}
return 0;
}</span>
J - Pahom on Water
一开始也没什么思路,看了别人题解后(http://blog.csdn.net/u011686226/article/details/25245465),思路完全通了。
K - Route Redundancy
这题的描述更是让人困惑。其实就是叫你求出最大流,然后除以某条路径中最大的流量。
在dfs里面判断一下,保存这个最大的流量。
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <queue>
using namespace std;
const int MAXPOINT = 1005;
const int MAXEDGE = 3000500;
const int INF = 0x7FFFFFF;
struct Edge
{
int to, cap, flow, next;
};
Edge edge[MAXEDGE];
struct Dinic
{
int n, m, s, t;
int srmaxflow;
bool vis[MAXPOINT];
int cur[MAXPOINT], head[MAXPOINT];
//single route max flow;
int d[MAXPOINT]; //distanct
Dinic(int n, int s, int t)
{
this -> n = n;
this -> s = s;
this -> t = t;
srmaxflow = m = 0;
memset(head, -1, sizeof(head));
}
void AddEdge(int u, int v, int cap)
{
edge[m] = (Edge){v, cap, 0, head[u]};
head[u] = m++;
edge[m] = (Edge){u, 0, 0, head[v]};
head[v] = m++;
}
bool bfs()
{
memset(vis, false, sizeof(vis));
queue <int> q;
q.push(s);
vis[s] = true;
while(!q.empty())
{
int u = q.front();
q.pop();
int next = head[u];
while(next != -1)
{
Edge& e = edge[next];
if(!vis[e.to] && e.cap > e.flow)
{
vis[e.to] = true;
d[e.to] = d[u] + 1;
q.push(e.to);
}
next = e.next;
}
}
return vis[t];
}
int dfs(int u, int a)
{
if(u == t || a == 0) return a;
int& next = cur[u];
int flow = 0, f;
while(next != -1)
{
Edge& e = edge[next];
if(d[e.to] == d[u] + 1 && (f = dfs(e.to, min(a, e.cap - e.flow))) > 0)
{
e.flow += f;
edge[next^1].flow -= f;
srmaxflow = max(srmaxflow, f);
a -= f;
flow += f;
if(a == 0) break;
}
next = e.next;
}
return flow;
}
float getMaxFlow()
{
float f = 0;
while(bfs())
{
for(int i = 0;i < n; i++)
cur[i] = head[i];
f += dfs(s, INF);
}
//if(srmaxflow == 0) return 0;
return f/srmaxflow;
}
};
int main()
{
int T;
cin>>T;
while(T--)
{
int d, n, e, s, t;
int u, v, cap;
scanf("%d%d%d%d%d",&d, &n, &e, &s, &t);
Dinic dinic(n, s, t);
for(int i = 0;i < e; i++)
{
scanf("%d%d%d", &u, &v, &cap);
dinic.AddEdge(u, v, cap);
}
printf("%d %.3f\n",d, dinic.getMaxFlow());
}
return 0;
}</span>
L - Marriage Match II
这题告诉我,求最大流时是不应该修改图的任何东西。任何求最大流的问题,总是先构好图,然后跑一遍最大流即得出结果。要改也要等当前的最大流跑完算法后再改。
http://blog.csdn.net/qq564690377/article/details/7857983
来源:CSDN
作者:u011580493
链接:https://blog.csdn.net/u011580493/article/details/37672381