POJ1149:PIGS——题解

http://poj.org/problem?id=1149

题目大意:

Mirko有M个猪圈和N个客户,猪圈里有特定数量的猪,每个客户按照顺序来买猪,他们只能打开他们能打开的猪圈,然后取走一些猪(上限为每个人特定的值b),Mirko在每个客户来之后将该客户打开的猪圈内剩余的猪重新分配到该客户打开的猪圈内。

求最大取猪数量。

——————————————————————

看到n和m的数量级为网络流。

很容易能够想到一种建图方法:

1.将每个猪圈拆成N个点(代表第N个客户来的时刻每个猪圈的状态),源点连到每个猪圈第一个点,权值为该猪圈内猪的数量,之后前点连到后点权值INF。

2.客户取猪就是将他们所属的猪圈中能打开的猪圈与客户连INF边,客户向汇点连b权值的边。

3.之后将此时所有打开的猪圈的前状态与每个后状态都连INF边。

好的讲了一通是不是很简单……显然不能这么做,点太多了,O(跑不过)。

我们在拆完点之后还需要缩点:

1.如果几个结点的流量来源相同则可以合并

2.如果几个结点的流量去处相同则可以合并

3.如果两点间有INF的边,且到的点没有其他的流量来源,则可以可以合并

根据这些再画画图就能做啦!

对于新的图,我们构造的思想:

1.对于每一个用户,流入他所有能开开的猪圈的猪。

2.对于其他用户与前面的用户的猪圈有交集的,可以从他们那里“得到”猪。

参考了:https://www.cnblogs.com/sleeper-qp/archive/2012/07/23/2605253.html

//注意:此模板默认源点为1,汇点为m 
#include <cstdio>
#include <iostream>
#include <cmath>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=210;
const int INF=2147483640;
inline int read(){
    int X=0,w=0;char ch=0;
    while(ch<'0'||ch>'9'){w|=ch=='-';ch=getchar();}
    while(ch>='0'&&ch<='9')X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
    return w?-X:X;
}
struct node{
    int next;
    int to;
    int w;
}edge[N*N*2];
int head[N],cnt=-1;
void add(int u,int v,int w){//u起点v终点w容量 
    cnt++;
    edge[cnt].to=v;
    edge[cnt].w=w;
    edge[cnt].next=head[u];
    head[u]=cnt;
    return;
}
int lev[N],cur[N];//lev层数,cur[i]为以i为起点的边的编号 
bool bfs(int m){//强制1为源点,m为汇点 
    int dui[m],r=0;//队列和右指针 
    for(int i=1;i<=m;i++){//初始化 
        lev[i]=-1;
        cur[i]=head[i];
    }
    dui[0]=1,lev[1]=0;
    int u,v;//u起点v终点 
    for(int l=0;l<=r;l++){//左指针 
        u=dui[l];
        for(int e=head[u];e!=-1;e=edge[e].next){
            v=edge[e].to;
            if(edge[e].w>0&&lev[v]==-1){//1.能走 2.未分层 
                lev[v]=lev[u]+1;
                r++;
                dui[r]=v;//v入队 
                if(v==m)return 1;//分层完毕 
            }
        }
    }
    return 0;//无法分层 
}
int dinic(int u,int flow,int m){//u当前点,flow为下面的点能够分配多大的流量,m终点 
    if(u==m)return flow;//终点直接全流入
    int res=0,delta;//res实际流量 
    for(int &e=cur[u];e!=-1;e=edge[e].next){//'&'相当于cur[u]=e;即流满的边不会再被扫一次 
        int v=edge[e].to;
        if(edge[e].w>0&&lev[u]<lev[v]){//只能从低层往高层流 
            delta=dinic(v,min(edge[e].w,flow-res),m); 
            if(delta>0){//如果增广 
                edge[e].w-=delta;//正向边容量减少 
                edge[e^1].w+=delta;//反向边仍量增加(暗示退流) 
                res+=delta;//扩张流量增加 
                if(res==flow)break;//可流的都流完了,及时跳出 
            }
        }
    }
    if(res!=flow)lev[u]=-1;//没流完,说明以后不能从这个点流出任何流量,那就不需要这个点了 
    return res;
}
int num[1001];
int vis[1001];
int mp[N];
int main(){
    int m=read();
    int n=read();
    int S=1,T=n+2;
    memset(head,-1,sizeof(head));
    for(int i=1;i<=m;i++)num[i]=read();
    for(int i=1;i<=n;i++){
    int a=read();
    for(int j=1;j<=a;j++){
        int p=read();
        if(!vis[p]){
        mp[i+1]+=num[p];
        vis[p]=i+1;
        }else{
        add(vis[p],i+1,INF);
        add(i+1,vis[p],0);
        }
    }
    int b=read();
    add(i+1,T,b);
    add(T,i+1,0);
    }
    for(int i=2;i<=n+1;i++){
    if(mp[i]){
        add(S,i,mp[i]);
        add(i,S,0);
    }
    }
    int ans=0;
    while(bfs(T))ans+=dinic(S,INF,T);
    printf("%d\n",ans);
    return 0;
}

 

posted @ 2017-11-30 11:57  luyouqi233  阅读(225)  评论(0编辑  收藏  举报