您的位置:首页 > 理论基础 > 计算机网络

网络流例题总结

2015-08-01 21:39 417 查看
最大流相关例题

POJ 3436

题意:

生产1台电脑需要n个机器,每个机器对运过来的未加工好的机器加工,每个机器每小时有最大的生产电脑量,每台电脑有p个部分,且每台机器只会接受满足条件的未加工好的电脑,问一小时内最大的生产量?

思路:

属于比较裸的最大流,按照条件建边即可,注意每一台机器有最大的生产量C,这个可以当做流入这台机器的最大流量,但是考虑特殊情况,我们必须去拆点才能建立正确的图,也就是每台机器拆成两个点,且这两个点之间的边的最大流量为C。E-K模板 get

int cap

, flow

;

int EdmondsKarp(int s, int t) {
int p
, f
;
queue<int> q;

memset(flow, 0, sizeof(flow));
int ans = 0;

while(true) {
memset(f, 0, sizeof(f));

f[s] = INF;

q.push(s);

while(!q.empty()) { //BFS 找增广路
int u = q.front(); q.pop();

for(int v=1; v<=n; v++) if(!f[v] && cap[u][v]>flow[u][v]){
//找到新节点v
p[v] = u; q.push(v);
f[v] = min(f[u], cap[u][v]-flow[u][v]);
}
}

if(f[t] == 0) break;    //找不到,则当前流已经是最大流

for(int u=t; u != s; u = p[u]) {    //从汇点往回走
flow[p[u]][u] += f[t];  //更新正向流量
flow[u][p[u]] -= f[t];  //更新反向流量
}

ans += f[t];  //更新从 s 流出的流量
}

return ans;
}


POJ 3281

题意:

有N头牛,每头牛只能吃一份含特定食物及饮料的套餐,且每种食物和饮料只能被一头牛吃,问最大能喂多少头牛?

思路:

一开始看上去以为是二分图匹配,但是明显分成两个图去匹配是会出现错误的,因为一头必须同时匹配食物和饮料。正确的解法就是网络流啦,建图思路比较巧妙,建议没太弄懂的好哈理解。但是一开始是这样建图源点-food-牛-drink-汇点,这样虽然满足每份food和drink只能给一头牛吃,但是没法解决每头牛只能吃一份的问题。如果是这样,源点-food-牛-牛-drink-汇点,将牛拆成两个点,里面的边权值全为1.用效率不是很高的Ek算法就能解决。所以最重要的建图过程。也是网络流的难点。

POJ 1087

题意:

给m个需要充电的装置,每个装置必须有对应的插座类型,现在只有n种插座,且每种插座只有一个,一个插座只能对应一个装置,现在有k种适配器u v ,可以使得类型为u的插座可以转换为类型为v的插座,且适配器之间可以嵌套使用,每种类型适配器数量无限,求最多能有多少充电装置成功充电?

思路:

二分匹配肯定是可以做的啦,不过需要加上k种电源适配器对应的边。网络流的做法呢,建图为源点 -> 充电装置 -> 插座 -> 汇点,因为充电装置只有一个,所以源点 -> 充电装置 边流量为1 ,而每种插座只能用一次,所以充电装置 -> 插座 边流量为1 ,而插座类型可以通过适配器等效的使用,所以由于加进适配器而增加的插座之间的边流量因为INF,但是注意一点就是插座 -> 汇点的建边,只能由那n个已有的插座向汇点建边,且流量为1。。。建图过程一定要理解。

POJ 2195

题意:

给定一个矩阵,知道人的位置和家的位置,每个家只能容纳一个人,且每个人到达一个家需要花费,求容纳最多人的最小花费?

思路:

比价明显的最小费用最大流,建图就是源点 -> 人 -> 家 -> 汇点,源点 -> 人,家 -> 汇点边的容量就是1,费用为0,而人 -> 家边的容量也是1,费用就是上面提到的花费了,然后跑一发最小费用流就行了!模板,get!

struct edge
{
int to,next,cap,flow,cost;
}e[M];

int uN,head
,tot;
int pre
,dis
;
bool vis
;
void init()
{
memset(head,-1,sizeof(head));
tot=0;
}
void addedge(int u,int v,int cap,int cost)
{
e[tot].to=v , e[tot].cap=cap , e[tot].cost=cost , e[tot].flow=0;
e[tot].next=head[u] , head[u]=tot++;
e[tot].to=u , e[tot].cap=0 , e[tot].cost=-cost , e[tot].flow=0;
e[tot].next=head[v] , head[v]=tot++;
}
bool spfa(int s,int t)
{
queue<int> q;
for(int i=0 ; i<uN ;i++)
{
dis[i]=INF , vis[i]=0 , pre[i]=-1;
}
dis[s] = 0;
vis[s] = 1;
q.push(s);

while(!q.empty())
{
int u=q.front(); q.pop();
vis[u] = 0;
for(int i = head[u]; i!=-1 ;i = e[i].next)
{
int v = e[i].to;
if(e[i].cap > e[i].flow && dis[v] > dis[u] + e[i].cost){
dis[v] = dis[u] + e[i].cost;
pre[v] = i;
if(!vis[v]) {
vis[v] = 1;
q.push(v);
}
}
}
}
return pre[t] != -1;
}
int MincostMaxflow(int s,int t,int &cost)
{
int flow = 0;
cost = 0;
while(spfa(s,t))
{
int Min_f = INF;
for(int i = pre[t];i != -1 ;i = pre[e[i^1].to])
{
if(Min_f> e[i].cap-e[i].flow)
Min_f = e[i].cap-e[i].flow;
}
for(int i = pre[t];i != -1 ;i = pre[e[i^1].to])
{
e[i].flow += Min_f;
e[i^1].flow -= Min_f;
cost += Min_f*e[i].cost;
}
flow += Min_f;
}
return flow;
}


POJ 2516

题意:

有n个店主,m个储存库,有k种物品,已知每个储存库k种物品的库存,以及每个店主k种物品的需求,和物品-店主-存储库一一对应的花费,求满足所有店主需求的情况下最小的花费是多少?

思路:

首先呢,这k种物品是独立互不影响的,所以我们单独考虑一种类型的物品,建图为源点->a储存库->b店主->c汇点。

a:cap=供应量,cost=0;

b:cap=供应量,cost=花费;

c:cap=需求量,cost=0;

其实中间这条边b的流量是任意的(当然不能小于min(供应量,需求量)),只要正确的确定连接源点,汇点的边的流量就行了。

POJ 1459,HDU 4280,HDU 4292

思路:

这三个题之所以放在一起,是因为都是比较裸的最大流,而且要使用高效模板哦!HDU 4292跟POJ 3281是一样的题,只不过数据范围扩大到了800的样子,所以EK算法就会T了,附上高效模板-非递归形式的dicnic,代码量比较大,T_T!

const int N=810,M=5+1e6,MOD=7+1e9;
struct Node
{
int from,to,next;
int cap;
}edge[M];
int tol;

int dep
;//dep为点的层次
int head
;

void init()
{
tol=0;
memset(head,-1,sizeof(head));
}
void addedge(int u,int v,int w)//第一条变下标必须为偶数
{
edge[tol].from=u;
edge[tol].to=v;
edge[tol].cap=w;//注意正向边流量值
edge[tol].next=head[u];
head[u]=tol++;

edge[tol].from=v;
edge[tol].to=u;
edge[tol].cap=0;//注意反向边的流量值
edge[tol].next=head[v];
head[v]=tol++;
}

int BFS(int start,int end)
{
int que
;
int front,rear;
front=rear=0;
memset(dep,-1,sizeof(dep));
que[rear++]=start;
dep[start]=0;
while(front!=rear)
{
int u=que[front++];
if(front==N)front=0;
for(int i=head[u];i!=-1;i=edge[i].next)
{
int v=edge[i].to;
if(edge[i].cap>0&&dep[v]==-1)
{
dep[v]=dep[u]+1;
que[rear++]=v;
if(rear>=N)rear=0;
if(v==end)return 1;
}
}
}
return 0;
}
int dinic(int start,int end)
{
int res=0;
int top;
int stack
;//stack为栈,存储当前增广路
int cur
;//存储当前点的后继
while(BFS(start,end))
{
memcpy(cur,head,sizeof(head));
int u=start;
top=0;
while(1)
{
if(u==end)
{
int min=INF;
int loc;
for(int i=0;i<top;i++)
if(min>edge[stack[i]].cap)
{
min=edge[stack[i]].cap;
loc=i;
}
for(int i=0;i<top;i++)
{
edge[stack[i]].cap-=min;
edge[stack[i]^1].cap+=min;
}
res+=min;
top=loc;
u=edge[stack[top]].from;
}
for(int i=cur[u];i!=-1;cur[u]=i=edge[i].next)
if(edge[i].cap!=0&&dep[u]+1==dep[edge[i].to])
break;
if(cur[u]!=-1)
{
stack[top++]=cur[u];
u=edge[cur[u]].to;
}
else
{
if(top==0)break;
dep[u]=-1;
u=edge[stack[--top]].from;
}
}
}
return res;
}


URAL 1774

题意:

一个理发师要给N个人服务,每个人要求只在时刻[s,t]内被服务两次,理发师同一时刻可以同时给k个人服务,问能否服务这N个人,可以的话,输出一种可行的方案。

思路:

艹,真正比赛的时候遇到网络流就跪了,这个锅是我的,比赛的时候就已经想的是以每个时刻也当做一个点,由n个人到其所对应的时间去建边,但是一个人要被服务两次,也就是这个人需要去选择两个不同的时刻去接受服务,就是这个地方一直没有想清楚,最后时刻也确实一直没有静下心来好好思考这个,所以。。。背锅了。。。。

其实只需要把流入每个人的流量改为2就行啦,真是傻逼,啊啊啊!!

最后就是所有时刻到汇点的流量为k,跑一发最大流就行了,E-K居然也能跑2000个点,日了狗了。。。。。。

SGU 326 Perspective

题意:

给定N个球队,已知每个球队现有的得分,还有每个球队还有多少比赛要打,还有这N个球队之间相互的比赛场数,问最终是否有可能球队1的得分最多或者并列最多?

思路:

*首先去贪心一下,得到球队1最多能得到的分数Max,很自然的是我们可以把两个队之间的比赛当作点,如果球队 i 与 j 之间有val场比赛,那么val这个值可以分配给 i 或 j ,但是为了满足条件(即球队1的得分最多或者并列最多),我们必须通过合理的分配使得另外 n−1 个球队里的最大值最小化,那么如何去做呢?想啊,想啊,想啊,就。。一直想不出来。。。

*那么我们不妨反过来考虑,也就是建边由:球队->比赛->汇点,而这些边的流量值为val,最后我们只需判断它是否满流即可(即 Maxflow=∑val ),由源点到比赛的边的流量为Max−r[i],也就是这支球队最多只能胜利的场数,一些细节稍微注意下就OK了!

*还是属于比较基础的题,这个题的启发就是正向建边不行的话,我们可以尝试反向去建边!还有就是这种最小化,最大值的问题。

以下为最小割相关例题

UVA 10480 Sabotage

题意:

给你一个简单连通的带权无向图,可以删除一些边使得点 S 和点 T 属于两个不相交的连通块,问最小花费为?所删除的边有哪些?

思路:

首先呢,得明白最小割的概念,这样的话,容易知道这就是一个裸的最小割模型,最小割=最大流,所以只需求一下最大流就可得到最小的花费,但是如何求这些割边呢?跑完最大流后,在残余网络上,用f[i]数组表示由 S 流向 i 的流量,初始化为f[S]=INF,其它为0,那么按照寻找增广路的过程,如果最终f[i]为0,是不是就说明了他是属于终点那一部分,

否则,说明流量还可以继续流向i点,毫无疑问他就属于起点那一部分了!

这里就学习到了一种寻找最小割下割边的方法,注意在不同模型下灵活使用

拓展:

ZOJ 3792 Romantic Value

题目意思一样,但是多了一条件,就是在保证最大流的情况下,还要求割边的数量尽可能的少,转化思路非常巧妙,在求最大流的时候就顺便求出了割边的数量,方法就是把边权 val 转化为 val∗1000+1 。

大叫三声,好!!!好!!!好!!!

不断更新中。。。。。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: