【POJ1149】PIGS-最大流+优化建模

测试地址:PIGS
题目大意:m个猪圈,每个猪圈内有一些猪,有n名顾客陆续到来,他们每个人能够开一个集合内的猪圈,并且能从这些猪圈中买走至多Bi头猪,在这个时候,我们还能对这些猪圈内的猪进行调动,每个猪圈内可以装无穷多头猪,问最多一共能卖出多少头猪。
做法:本题需要用到最大流+优化建模。
看到这题,一个很显然的思路是,对每一天建出m个点表示这一天各猪圈内的猪,然后各种常规建图后跑最大流,然而我们发现这个图光点数就有nm个了,边就更多了,根本没法做,因此我们要考虑优化我们的建模。
我们可以用“合并”的思想来减少图中的点和边。有以下三个显然的结论:
1.如果一些点的入度来源完全相同,那么这些点能合并成一个点。
2.如果一些点的出度指向完全相同,那么这些点能合并成一个点。
3.如果从一个点到另一个点有且仅有一条路径,那么可以把从这个点到另一个点之间的路径合并为一条从这个点到另一个点的边,容量为原路径上容量的最小值。
当然,这些结论是用来帮助我们减小网络的复杂性,从而想到更好的建模方法,而不是让我们在实际程序中用这些方法缩点。总之经过这些变换后,我们得到了一个新的可以描述的模型,具体来说如下:
我们考虑每份从源点到汇点的流量都代表一头猪被卖掉了,那么原来在猪圈i内的猪能在哪些时候被调动到哪些猪圈去呢?令第i位顾客能开的猪圈集合为Si,若SiSj相交,就表示第i天时集合i中的任何一个猪圈中的猪能经过调动最终到达第j天时集合Sj中的任何一个猪圈,因此我们对每个顾客建一个点,若SiSj相交则从顾客i到顾客j连一条容量为正无穷的边(具体建模时操作略有不同,但是和这样建边等价,详见代码)。这样只要能从一个顾客走到另一个顾客,就表示到顾客i时能卖的猪,到顾客j时也能卖。特别地,我们要从源点向每个顾客连一些边,连边方式为:如果猪圈i第一次被开是在第j天,那么从源点向顾客j连一条容量为一开始猪圈i中的猪数。最后显然,我们要从每个顾客向汇点连一条容量为他想买的猪数的边。对这个网络跑一个最大流即可。
我们发现优化后的建模仅包含n+2个点,是非常优的,因此我们能通过此题。
以下是本人代码:

#include <bits/stdc++.h>
using namespace std;
const int inf=1000000000;
int m,n,S,T,p[1010],last[1010];
int first[110]={0},tot=1;
int lvl[110],cur[110],q[110],h,t;
bool vis[110];
struct edge
{
    int v,next,f;
}e[100010];

void insert(int a,int b,int f)
{
    e[++tot].v=b,e[tot].next=first[a],e[tot].f=f,first[a]=tot;
    e[++tot].v=a,e[tot].next=first[b],e[tot].f=0,first[b]=tot;
}

void init()
{
    scanf("%d%d",&m,&n);
    S=n+1,T=n+2;
    for(int i=1;i<=m;i++)
    {
        scanf("%d",&p[i]);
        last[i]=S;
    }
    for(int i=1;i<=n;i++)
    {
        int a,b,x;
        memset(vis,0,sizeof(vis));
        scanf("%d",&a);
        while(a--)
        {
            scanf("%d",&x);
            if (last[x]==S) insert(S,i,p[x]);
            else if (!vis[last[x]])
            {
                vis[last[x]]=1;
                insert(last[x],i,inf);
            }
            last[x]=i;
        }
        scanf("%d",&b);
        insert(i,T,b);
    }
} 

bool makelevel()
{
    for(int i=1;i<=T;i++)
        lvl[i]=-1,cur[i]=first[i];
    h=t=1;
    q[1]=S;
    lvl[S]=0;
    while(h<=t)
    {
        int v=q[h++];
        for(int i=first[v];i;i=e[i].next)
            if (e[i].f&&lvl[e[i].v]==-1)
            {
                lvl[e[i].v]=lvl[v]+1;
                q[++t]=e[i].v;
            }
    }
    return lvl[T]!=-1;
}

int maxflow(int v,int maxf)
{
    if (v==T) return maxf;
    int ret=0,f;
    for(int i=cur[v];i;i=e[i].next)
    {
        if (e[i].f&&lvl[e[i].v]==lvl[v]+1)
        {
            f=maxflow(e[i].v,min(maxf-ret,e[i].f));
            ret+=f;
            e[i].f-=f;
            e[i^1].f+=f;
            if (ret==maxf) break;
        }
        cur[v]=i;
    }
    if (!ret) lvl[v]=-1;
    return ret;
}

void dinic()
{
    int maxf=0;
    while(makelevel())
        maxf+=maxflow(S,inf);
    printf("%d",maxf);
}

int main()
{
    init();
    dinic();

    return 0;
}
posted @ 2018-04-30 09:20  Maxwei_wzj  阅读(147)  评论(0编辑  收藏  举报