poj 1149 PIGS(最大流+经典构图)

46 篇文章 0 订阅
PIGS
Time Limit: 1000MS Memory Limit: 10000K
Total Submissions: 14578 Accepted: 6479

Description

Mirko works on a pig farm that consists of M locked pig-houses and Mirko can't unlock any pighouse because he doesn't have the keys. Customers come to the farm one after another. Each of them has keys to some pig-houses and wants to buy a certain number of pigs.
All data concerning customers planning to visit the farm on that particular day are available to Mirko early in the morning so that he can make a sales-plan in order to maximize the number of pigs sold.
More precisely, the procedure is as following: the customer arrives, opens all pig-houses to which he has the key, Mirko sells a certain number of pigs from all the unlocked pig-houses to him, and, if Mirko wants, he can redistribute the remaining pigs across the unlocked pig-houses.
An unlimited number of pigs can be placed in every pig-house.
Write a program that will find the maximum number of pigs that he can sell on that day.

Input

The first line of input contains two integers M and N, 1 <= M <= 1000, 1 <= N <= 100, number of pighouses and number of customers. Pig houses are numbered from 1 to M and customers are numbered from 1 to N.
The next line contains M integeres, for each pig-house initial number of pigs. The number of pigs in each pig-house is greater or equal to 0 and less or equal to 1000.
The next N lines contains records about the customers in the following form ( record about the i-th customer is written in the (i+2)-th line):
A K1 K2 ... KA B It means that this customer has key to the pig-houses marked with the numbers K1, K2, ..., KA (sorted nondecreasingly ) and that he wants to buy B pigs. Numbers A and B can be equal to 0.

Output

The first and only line of the output should contain the number of sold pigs.

Sample Input

3 3
3 1 10
2 1 2 2
2 1 3 3
1 2 6

Sample Output

7
 
一下转自:http://www.cppblog.com/y346491470/articles/152830.html
【题意】:有 M 个猪圈(M ≤ 1000),每个猪圈里初始时有若干头猪。开始所有猪圈都是关闭的。依次来了 N 个顾客(N ≤ 100),每个顾客分别会打开指定的几个猪圈,从中买若干头猪。每个顾客分别都有他能够买的数量的上限。每个顾客走后,他打开的那些猪圈中的猪,都可以被任意地调换到其它开着的猪圈里,然后所有猪圈重新关上。  问总共最多能卖出多少头猪。

【题解】:举个例子来说。有 3 个猪圈,初始时分别有 3、 1 和 10 头猪。依次来了 3 个顾客,第一个打开 1 号 和 2 号猪圈,最多买 2 头;第二个打开 1 号 和 3 号猪圈,最多买 3 头;第三个打开 2 号猪圈,最多买 6 头。那么,最好的可能性之一就是第一个顾客从 1 号圈买 2 头,然后把 1 号圈剩下的 1 头放到 2 号圈;第二个顾客从 3 号圈买 3 头;第三个顾客从 2 号圈买 2 头。总共卖出 2 + 3 + 2 = 7 头。□

    不难想像,这个问题的网络模型可以很直观地构造出来。就拿上面的例子来说,可以构造出图1所示的模型(图中凡是没有标数字的边,容量都是 +∞):

 
 
  • 三个顾客,就有三轮交易,每一轮分别都有 3 个猪圈和 1 个顾客的节点。
  • 从源点到第一轮的各个猪圈各有一条边,容量就是各个猪圈里的猪的初始数量。
  • 从各个顾客到汇点各有一条边,容量就是各个顾客能买的数量上限。
  • 在某一轮中,从该顾客打开的所有猪圈都有一条边连向该顾客,容量都是 +∞。
  • 最后一轮除外,从每一轮的 i 号猪圈都有一条边连向下一轮的 i 号猪圈,容量都是 +∞,表示这一轮剩下的猪可以留到下一轮。
  • 最后一轮除外,从每一轮被打开的所有猪圈,到下一轮的同样这些猪圈,两两之间都要连一条边,表示它们之间可以任意流通。
图 1
    不难想像,这个网络模型的最大流量就是最多能卖出的数量。图中最多有 2 + N + M × N ≈ 100,000 个节点。□     这个模型虽然很直观,但是节点数太多了,计算速度肯定会很慢。其实不用再想别的算法,就让我们继续上面的例子,用合并的方法来简化这个网络模型。     首先,最后一轮中没有打开的猪圈就可以从图中删掉了,也就是中图2红色的部分,显然它们对整个网络的流量没有任何影响。
图 2
    接着,看图 2蓝色的部分。根据我总结出的以下几个规律,可以把这 4 个点合并成一个:     规律 1. 如果几个节点的流量的来源完全相同,则可以把它们合并成一个。     规律 2. 如果几个节点的流量的去向完全相同,则可以把它们合并成一个。     规律 3. 如果从点 u 到点 v 有一条容量为 +∞ 的边,并且 u 是 v 的唯一流量来源,或者 v 是 u 的唯一流量去向,则可以把 u 和 v 合并成一个节点。     根据规律1,可以把蓝色部分右边的 1、 2 号节点合并成一个;根据规律2,可以把蓝色部分左边的 1、 2 号节点合并成一个;最后,根据规律3,可以把蓝色部分的左边和右边(已经分别合并成了一个节点)合并成一个节点。于是,图2被简化成了图3的样子。也就是说,最后一轮除外,每一轮被打开的猪圈和下一轮的同样这些猪圈都可以被合并成一个点。
图 3

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

图 4
    让我们从图4中重新总结一下构造这个网络模型的规则:
  • 每个顾客分别用一个节点来表示。
  • 对于每个猪圈的第一个顾客,从源点向他连一条边,容量就是该猪圈里的猪的初始数量。如果从源点到一名顾客有多条边,则可以把它们合并成一条,容量相加。
  • 对于每个猪圈,假设有 n 个顾客打开过它,则对所有整数 i ∈ [1, n),从该猪圈的第 i 个顾客向第 i + 1 个顾客连一条边,容量为 +∞。
  • 从各个顾客到汇点各有一条边,容量是各个顾客能买的数量上限。
    拿我们前面一直在讲的例子来说:1 号猪圈的第一个顾客是 1 号顾客,所以从源点到 1 号顾客有一条容量为 3 的边;1 号猪圈的第二个顾客是 2 号顾客,因此从 1 号顾客到 2 号顾客有一条容量为 +∞ 的边;2 号猪圈的第一个顾客也是 1 号顾客,所以从源点到 1 号顾客有一条容量为 1 的边,和之前已有的一条边合并起来,容量变成 4;2 号猪圈的第二个顾客是 3 号顾客,因此从 1 号顾客到 3 号顾客有一条容量为 +∞ 的边;3 号猪圈的第一个顾客是 2 号顾客,所以从源点到 2 号顾客有一条容量为 10 的边。□     新的网络模型中最多只有 2 + N = 102 个节点,计算速度就可以相当快了。可以这样理解这个新的网络模型:对于某一个顾客,如果他打开了猪圈 h,则在他走后,他打开的所有猪圈里剩下的猪都有可能被换到 h 中,因而这些猪都有可能被 h 的下一个顾客买走。所以对于一个顾客打开的所有猪圈,从该顾客到各猪圈的下一个顾客,都要连一条容量为 +∞ 的边。     在面对网络流问题时,如果一时想不出很好的构图方法,不如先构造一个最直观,或者说最“硬来”的模型,然后再用合并节点和边的方法来简直化这个模型。经过简化以后,好的构图思路自然就会涌现出来了。这是解决网络流问题的一个好方法。
 
AC代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <queue>
#include <stack>
#include <vector>
#include <map>
#include <cmath>
#include <cstdlib>
#define L(rt) (rt<<1)
#define R(rt) (rt<<1|1)
#define ll long long
#define eps 1e-6
using namespace std;

const int INF=1000000000;
const int maxn=1100;
struct Edge{
    int u,v,cap,flow,next;
}et[maxn*2];
int low[maxn],cnt[maxn],dis[maxn],cur[maxn],pre[maxn],eh[maxn];
int hcap[maxn];
bool vis[maxn];
int n,m,s,t,num;
void init(){
    memset(eh,-1,sizeof(eh));
    num = 0;
}
void add(int u,int v,int cap,int flow){
    Edge e = {u,v,cap,flow,eh[u]};
    et[num] = e;
    eh[u] = num ++;
}
void addedge(int u,int v,int cap){
    add(u,v,cap,0);
    add(v,u,0,0);
}
int isap(int s,int t,int nv)
{
    int u,v,now,flow = 0;
    memset(cnt,0,sizeof(cnt));
    memset(dis,0,sizeof(dis));
    memset(low,0,sizeof(low));
    for(u = 0; u <= nv; u ++) cur[u] = eh[u];
    low[s] = INF, cnt[0] = nv, u = s;
    while(dis[s]<nv)
    {
        for(now = cur[u]; now != -1; now = et[now].next)
        if(et[now].cap-et[now].flow&&dis[u] == dis[v = et[now].v]+1) break;
        if(now != -1)
        {
            cur[u] = pre[v] = now;
            low[v] = min(et[now].cap-et[now].flow,low[u]);
            u = v;
            if(u == t)
            {
                for(;u != s; u = et[pre[u]].u)
                {
                    et[pre[u]].flow += low[t];
                    et[pre[u]^1].flow -= low[t];
                }
                flow += low[t];
                low[s] = INF;
            }
        }
        else
        {
            if(--cnt[dis[u]] == 0) break;
            dis[u] = nv, cur[u] = eh[u];
            for(now = eh[u]; now != -1; now = et[now].next)
            if(et[now].cap-et[now].flow&&dis[u] > dis[et[now].v]+1)
            dis[u] = dis[et[now].v]+1;
            cnt[dis[u]] ++;
            if(u != s) u = et[pre[u]].u;
        }
    }
    return flow;
}
int main()
{
    int nkey,h,cap;
    while(~scanf("%d%d",&m,&n))
    {
        init();
        s=0;
        t=n+1;
        for(int i = 1; i <= m; i ++)
        scanf("%d",&hcap[i]);
        memset(vis,false,sizeof(vis));
        for(int i = 1; i <= n; i ++)
        {
            scanf("%d",&nkey);
            while(nkey --)
            {
                scanf("%d",&h);
                if(!vis[h])
                {
                    vis[h]=true;
                    addedge(s,i,hcap[h]);
                }
                else addedge(pre[h],i,INF);
                pre[h]=i;
            }
            scanf("%d",&cap);
            addedge(i,t,cap);
        }
        printf("%d\n",isap(s,t,t+1));
    }
    return 0;
}


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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值