[poj 1149]PIGS[网络流][Edmonds-Karp][Dinic]

【题目大意】

有M个猪圈,每个猪圈里初始时有若干头猪。一开始所有猪圈都是关闭的。依次来了N个顾客,每个顾客分别会打开指定的几个猪圈,从中买若干头猪。每个顾客分别都有他能够买的数量的上限。每个顾客走后,他打开的那些猪圈中的猪,都可以被任意地调换到其它开着的猪圈里,然后所有猪圈重新关上。问总共最多能卖出多少头猪。(1 <= N <= 100, 1 <= M <= 1000)

【建模方法】
不难想象,这个问题的网络模型可以很直观地构造出来。就拿上面的例子来说,可以构造出图1所示的模型(图中凡是没有标数字的边,容量都是∞):
• 三个顾客,就有三轮交易,每一轮分别都有3个猪圈和1个顾客的结点。
• 从源点到第一轮的各个猪圈各有一条边,容量就是各个猪圈里的猪的初始数量。
• 从各个顾客到汇点各有一条边,容量就是各个顾客能买的数量上限。
• 在某一轮中,从该顾客打开的所有猪圈都有一条边连向该顾客,容量都是∞。
• 最后一轮除外,从每一轮的i号猪圈都有一条边连向下一轮的i号猪圈,容量都是∞,表示这一轮剩下的猪可以留到下一轮。
• 最后一轮除外,从每一轮被打开的所有猪圈,到下一轮的同样这些猪圈,两两之间都要连一条边,表示它们之间可以任意流通。

节点合并

规律1. 如果几个结点的流量的来源完全相同,则可以把它们合并成一个。
规律2. 如果几个结点的流量的去向完全相同,则可以把它们合并成一个。
规律3. 如果从点u到点v有一条容量为∞的边,并且点v除了点u以外没有别的流量来源,则可以把这两个结点合并成一个。

让我们从图4中重新总结一下构造这个网络模型的规则:
• 每个顾客分别用一个结点来表示。
• 对于每个猪圈的第一个顾客,从源点向他连一条边,容量就是该猪圈里的猪的初始数量。如果从源点到一名顾客有多条边,则可以把它们合并成一条,容量相加。
• 对于每个猪圈,假设有n个顾客打开过它,则对所有整数i∈[1, n),从该猪圈的第i个顾客向第i + 1个顾客连一条边,容量为∞。
• 从各个顾客到汇点各有一条边,容量是各个顾客能买的数量上限。

<以上来自 [ 网络流建模汇总 ] [ Edelweiss ]    Orz >

相当于首顾客是中转站, 是他可开猪圈中猪数之和.

之后的顾客从首顾客中取出(若涉及首顾客所代表的猪圈)

若有新圏被打开, 则该顾客将成为新圏的中转站,

每个顾客向汇点连一条边, 边权为可买数, 表示此顾客作为中转站可以经手的猪数很多, 但带回家的数目是有限的.


前向星

[Edmonds-Karp]


//156K 0MS

#include <cstdio>
#include <cstring>
#include <queue>
#include <algorithm>
using namespace std;

const int INF = 0x3f3f3f3f;
const int MAXN = 105;
const int MAXM = 1005;
struct pool
{
    int v,pre,w;
}p[MAXN*MAXN*2];
int num,head[MAXN],pig[MAXM],flow[MAXN],pre[MAXN],M,N,S,T,opened[MAXM];
bool vis[MAXN];
queue<int> q;

void clear()
{
    num = 1;
    memset(head,0,sizeof(head));
    memset(opened,false,sizeof(opened));
}

void add(int u,int v,int w)
{
    p[++num].v = v, p[num].w = w, p[num].pre = head[u], head[u] = num;
}

int bfs()
{
    while(!q.empty())   q.pop();
    memset(flow,0,sizeof(flow));
    memset(vis,false,sizeof(vis));
    vis[S] = true;
    flow[S] = INF;
    q.push(S);
    while(!q.empty())
    {
        int u = q.front(); q.pop();
        for(int tmp=head[u],k;k=p[tmp].v,tmp;tmp=p[tmp].pre)
        {
            if(!vis[k]&&p[tmp].w>0)///选择可以流的管道进行更新
            {
                vis[k] = true;
                q.push(k);
                pre[k] = u;
                flow[k] = min(p[tmp].w,flow[u]);
            }
        }
    }
    return flow[T];
}

int Edmonds_Karp()
{
    int u,f,MaxFlow = 0;
    while((f=bfs()))
    {
        for(u=T;u>S;u=pre[u])
        {
            for(int tmp=head[pre[u]],k;k=p[tmp].v,tmp;tmp=p[tmp].pre)
            {
                if(k!=u) continue;
                p[tmp].w -= f;
                p[tmp^1].w += f;
                break;
            }
        }
        MaxFlow += f;
    }
    return MaxFlow;
}

int main()
{
    while(scanf("%d %d",&M,&N)==2)
    {
        S = 0;
        T = N+1;
        clear();
        for(int i=1;i<=M;i++)
            scanf("%d",pig+i);
        for(int i=1,a,u;i<=N;i++)
        {
            scanf("%d",&a);
            int MaxWeigh = 0;
            while(a--)
            {
                scanf("%d",&u);
                if(!opened[u])
                {
                    MaxWeigh += pig[u];
                    opened[u] = i;
                }
                else
                {
                    add(opened[u],i,INF);
                    add(i,opened[u],0);
                }
            }
            if(MaxWeigh)
                add(S,i,MaxWeigh), add(i,S,0);
            scanf("%d",&a);
            add(i,T,a);
            add(T,i,0);
        }
        printf("%d\n",Edmonds_Karp());
    }
}

[Dinic]


//140K 16MS

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
using namespace std;
int num, M, N, source, sink;
const int INF = 0x3f3f3f3f;
const int MAXN = 105;
const int MAXM = 1005;
int pig[MAXM],opened[MAXM];
/* The arc of the flow network.*/
struct Pool
{
    int next, t, c;
} edge[MAXM];
/* The point of the flow network.*/
struct Point
{
    int son, cur, pre, lim, d;
} a[MAXN];

int que[MAXN], fi, la;
bool vis[MAXN];

/* Build the hierarchical graph for the algorithm*/
bool build()
{
    memset(vis, 0, sizeof (vis));
    que[fi=la=0]=sink;//reverse
    a[sink].d=0, a[sink].cur=a[sink].son, vis[sink]=true;
    while (fi<=la)
    {
        int v=que[fi++];
        for (int now=a[v].son, u; u=edge[now].t, now; now=edge[now].next)
            if (edge[now^1].c&&!vis[u])//BFS来分层,这里和EK相同
            {//倒着BFS的话,当然引用的还是对侧边,即正向边
                a[u].d=a[v].d+1;//越向前标号渐大
                a[u].cur=a[u].son;//cur指向头
                vis[u]=true;//已遍历
                que[++la]=u;//入队
            }
        if (vis[source])return true;//层次图向前已经扩展到源点
    }
    return false;
}


/*Use the Dinic algorithm to calculate the max flow.*/
int MaxFlow()
{
    int u, v, now, ret=0;
    while (build())
    {
        a[u=source].lim=INF;
        while (true)
        {
            for (now=a[u].cur; v=edge[now].t, now; now=edge[now].next)//cur优化
                if (edge[now].c&&a[u].d==a[v].d+1)break;//找到了一个子节点属于层次图
            if (now)
            {
                a[u].cur=edge[now].next;//下一次从这一条边的下一条边开始dfs
                a[v].pre=now;//指向v的边的指针
                a[v].lim=min(a[u].lim, edge[now].c);///更新到此处为止流的上限
                if ((u=v)==sink)//如果已经找到了一条增广路(走到了尽头)
                ///注意这个地方借判断语句, 将u下移, 便于判断为否的时候回到上面进入下一层!
                {//进行增广
                    do
                    {
                        edge[a[u].pre].c-=a[sink].lim;
                        edge[a[u].pre^1].c+=a[sink].lim;//这两句和Edmonds-Karp是一样的,增广
                        u=edge[a[u].pre^1].t;//找前驱~!
                    } while (u!=source);
                    ret+=a[sink].lim;//增广完毕之后累加新找到的流
                }//否则(没走到尽头)继续向下DFS
            }
            else//没有子节点属于层次图
            {
                if (u==source)break;//已经退到了源,则已找到最大流,算法结束
                a[u].cur=now;//=0,此节点被废弃,子代亦然
                u=edge[a[u].pre^1].t;//根据反向边找到前驱~!
            }
        }
    }
    return ret;
}
void clear()
{
    num = 1;
    memset(a, 0, sizeof (a));
    memset(opened,false,sizeof(opened));
}

/* Add an arc to the flow network.*/
void add(int x, int y, int z)
{
    edge[++num].t=y;
    edge[num].c=z;
    edge[num].next=a[x].son;//相当于pool的head数组
    a[x].son=num;
}

int main()
{
    while(scanf("%d %d",&M,&N)==2)
    {
        source = 0;
        sink = N+1;
        clear();
        for(int i=1;i<=M;i++)
            scanf("%d",pig+i);
        for(int i=1,a,u;i<=N;i++)
        {
            scanf("%d",&a);
            int MaxWeigh = 0;
            while(a--)
            {
                scanf("%d",&u);
                if(!opened[u])
                {
                    MaxWeigh += pig[u];
                    opened[u] = i;
                }
                else
                {
                    add(opened[u],i,INF);
                    add(i,opened[u],0);
                }
            }
            if(MaxWeigh)
                add(source,i,MaxWeigh), add(i,source,0);
            scanf("%d",&a);
            add(i,sink,a);
            add(sink,i,0);
        }
        printf("%d\n",MaxFlow());
    }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值