poj-1149-PIGS --网络流

Poj 1149pigs

题目大意
Mirko养着一些猪 猪关在一些猪圈里面 猪圈是锁着的 他自己没有钥匙(汗)
只有要来买猪的顾客才有钥匙
顾客依次来 每个顾客会用他的钥匙打开一些猪圈 买走一些猪 然后锁上
在锁上之前 Mirko有机会重新分配这几个已打开猪圈的猪
现在给出一开始每个猪圈的猪数 每个顾客所有的钥匙和要买走的猪数 问Mirko最多能卖掉几头猪

数据规模
猪圈数n<=1000
顾客数m<=100
每个猪圈的猪数不超过1000
假设猪圈容量无限

样例

3 3
3 1 10
2 1 2 2
2 1 3 3
1 2 6
3个猪圈分别有3,1,10头猪
第一个顾客有1,2猪圈的钥匙要买2头
第二个顾客有1,3猪圈的钥匙要买3头
第三个顾客有2猪圈的钥匙要买6头

上面的样例可以构造出下面的模型,(图中凡是没有标数字的边,容量都是+∞):



三个顾客,就有三轮交易,每一轮分别都有3个猪圈和 1个顾客的节点。 

1)三个顾客,就有三轮交易,每一轮分别都有3 个猪圈和1个顾客的节点。

2)从源点到第一轮的各个猪圈各有一条边,容量就是各个猪圈里的猪的初始数量。

3)从各个顾客到汇点各有一条边,容量就是各个顾客能买的数量上限。

4)在某一轮中,从该顾客打开的所有猪圈都有一条边连向该顾客,容量都是+∞

5)最后一轮除外,从每一轮的i号猪圈都有一条边连向下一轮的i号猪圈,容量都是+∞,表示这一轮剩下的猪可以留到下一轮。

6)最后一轮除外,从每一轮被打开的所有猪圈,到下一轮的同样这些猪圈,两两之间都要连一条边,容量+∞表示它们之间可以任意流通。 


这个网络模型的最大流量就是最多能卖出的数量。图中最多有2 + N + M × N ≈ 100,000个节点。

   这个模型虽然很直观,但是节点数太多了,计算速度肯定会很慢。其实不用再想别的算法,就让我们继续上面的例子,用合并的方法来简化这个网络模型。

   首先,最后一轮中没有打开的猪圈就可以从图中删掉了,也就是下面图中红色的部分,显然它们对整个网络的流量没有任何影响。

这个网络模型的最大流量就是最多能卖出的数量。图中最多有2 + N + M × N ≈ 100,000个节点。

   这个模型虽然很直观,但是节点数太多了,计算速度肯定会很慢。其实不用再想别的算法,就让我们继续上面的例子,用合并的方法来简化这个网络模型。

   首先,最后一轮中没有打开的猪圈就可以从图中删掉了,也就是下面图中红色的部分,显然它们对整个网络的流量没有任何影响。



用以下3条规律合并节点:

   
规律1.如果几个节点的流量的来源完全相同,则可以把它们合并成一个。

   
规律2.如果几个节点的流量的去向完全相同,则可以把它们合并成一个。

   
规律3.如果从点u 到点v 有一条容量为+∞的边,并且uv 唯一流量来源,或者vu 唯一流量去向,则可以把uv 合并成一个节点。


  根据规律1,可以把蓝色部分右边的12 号节点合并成一个;根据规律2,可以把蓝色部分左边的12 号节点合并成一个;最后,根据规律3,可以把蓝色部分的左边和右边(已经分别合并成了一个节点)合并成一个节点。于是,会得到下页的图。也就是说,最后一轮除外,每一轮被打开的猪圈和下一轮的同样这些猪圈都可以被合并成一个点。





接着,根据规律3,此中的蓝色节点、2号猪圈和 1号顾客这三点可以合并成一个;两个 3号猪圈和 2号顾客也可以合并成一个点。当然,如果两点之间有多条同向的边,则这些边可以合并成一条,容量相加。最终,上例中的网络模型被简化成了下页图的样子。



从上图重新总结一下构造这个网络流模型的规则:

1)每个顾客分别用一个节点来表示。

2)对于每个猪圈的第一个顾客,从源点向他连一条边,容量就是该猪圈里的猪的初始数量。如果从源点到一名顾客有多条边,则可以把它们合并成一条,容量相加。

3)对于每个猪圈,假设有n 个顾客打开过它,则对所有整数i ∈[1, n),从该猪圈的第i个顾客向第 i + 1个顾客连一条边,容量为 +∞

4)从各个顾客到汇点各有一条边,容量是各个顾客能买的数量上限。

 

新的网络模型中最多只有2 + N= 102 个节点,计算速度就可以相当快了。可以这样理解这个新的网络模型:对于某一个顾客,如果他打开了猪圈h,则在他走后,他打开的所有猪圈里剩下的猪都有可能被换到 h 中,因而这些猪都有可能被 h 的下一个顾客买走。所以对于一个顾客打开的所有猪圈,从该顾客到各猪圈的下一个顾客,都要连一条容量为+∞的边。

   在面对网络流问题时,如果一时想不出很好的构图方法,不如先构造一个最直观的模型,然后再用合并节点和边的方法来简直化这个模型。经过简化以后,好的构图思路自然就会涌现出来了。这是解决网络流问题的一个好方法。

代码:


#include<cstdio>
#include<cstring>
#include<iostream>
#include<cstdlib>
#include<algorithm>
using namespace std;

const int maxnode = 100+5;
const int maxedge = 10000 + 5;
const int oo = 1000000000;
int node, src, dest, nedge;
int head[maxnode], point[maxedge], next1[maxedge], flow[maxedge], capa[maxedge];//point[x]==y表示第x条边连接y,head,next为邻接表,flow[x]表示x边的动态值,capa[x]表示x边的初始值
int dist[maxnode], Q[maxnode], work[maxnode];//dist[i]表示i点的等级
void init(int _node, int _src, int _dest){//初始化,node表示点的个数,src表示起点,dest表示终点
    node = _node;
    src = _src;
    dest = _dest;
    for (int i = 0; i < node; i++) head[i] = -1;
    nedge = 0;
}
void addedge(int u, int v, int c1, int c2){//增加一条u到v流量为c1,v到u流量为c2的两条边
    point[nedge] = v, capa[nedge] = c1, flow[nedge] = 0, next1[nedge] = head[u], head[u] = (nedge++);
    point[nedge] = u, capa[nedge] = c2, flow[nedge] = 0, next1[nedge] = head[v], head[v] = (nedge++);
}
bool dinic_bfs(){
    memset(dist, 255, sizeof (dist));
    dist[src] = 0;
    int sizeQ = 0;
    Q[sizeQ++] = src;
    for (int cl = 0; cl < sizeQ; cl++)
        for (int k = Q[cl], i = head[k]; i >= 0; i = next1[i])
            if (flow[i] < capa[i] && dist[point[i]] < 0){
                dist[point[i]] = dist[k] + 1;
                Q[sizeQ++] = point[i];
            }
    return dist[dest] >= 0;
}
int dinic_dfs(int x, int exp){
    if (x == dest) return exp;
    for (int &i = work[x]; i >= 0; i = next1[i]){
        int v = point[i], tmp;
        if (flow[i] < capa[i] && dist[v] == dist[x] + 1 && (tmp = dinic_dfs(v, min(exp, capa[i] - flow[i]))) > 0){
            flow[i] += tmp;
            flow[i^1] -= tmp;
            return tmp;
        }
    }
    return 0;
}
int dinic_flow(){
    int result = 0;
    while (dinic_bfs()){
        for (int i = 0; i < node; i++) work[i] = head[i];
        while (1){
            int delta = dinic_dfs(src, oo);
            if (delta == 0) break;
            result += delta;
        }
    }
    return result;
}
//建图前,运行一遍init();
//加边时,运行addedge(a,b,c,0),表示点a到b流量为c的边建成(注意点序号要从0开始)
//求解最大流运行dinic_flow(),返回值即为答案
int p[105][1005];
int zhu[1005],gu[105];
int f[1005];
int ans[105];
int main()
{
	int n,m;
	int i,j,k;
	cin>>m>>n;
	for(i=1;i<=m;i++) cin>>zhu[i];
	memset(p,0,sizeof(p));
	memset(f,0,sizeof(f));
	memset(ans,0,sizeof(ans));
	for(i=1;i<=n;i++)
	{
		int a,t;
		cin>>a;
		while(a--)
		{
			cin>>t;
			p[i][t]=1;
		}
		cin>>gu[i];
	}
	for(i=1;i<=n;i++)
	{
		for(j=1;j<=m;j++)
		{
			if(p[i][j]==1&&f[j]==0)
			{
				ans[i]+=zhu[j];
				f[j]=1;
			}
		}
	}
	init(n+2,0,n+1);
	for(i=1;i<=n;i++){
		addedge(i,n+1,gu[i],0);
		if(ans[i]>0)
			addedge(0,i,ans[i],0);
	}
	int tt;
	for(i=1;i<=m;i++)
	{
		tt=-1;
		for(j=1;j<=n;j++)
		{
			if(p[j][i]==1)
			{
				if(tt==-1)
					tt=j;
				else{
					addedge(tt,j,oo,0);
					tt=j;
				}
			}
		}
	}
	int he=dinic_flow();
	cout<<he<<endl;
	//system("pause");
}


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值