poj 3436 ACM Computer Factory

Problem

poj.org/problem?id=3436
vjudge.net/contest/68128#problem/A

Reference

POJ 3436 ACM Computer Factory(最大流)

Meaning

电脑由 p 个部件组成。有 n 台组装电脑的机器,每台机器有 3 种参数:

  1. Qi ,表示最大性能,每小时装 Qi 台电脑;
  2. P 个数 Si,1 ,…, Si,p ,表示输入电脑的组成,有 3 种情况:
    • 0:该部件不能有
    • 1:该部件必须有
    • 2:该部件可有可无
  3. P 个数 Di,1 ,…, Di,p ,表示组装完后电脑的组成,有 2 种情况:
    • 0:该部件不存在
    • 1:该部件存在

输入中没有 1 的就可以是原始输入,输出中全是 1 的就是最终输出。
现在要在这些机器间加边,使得总的生产效率最大。

Analysis

可能有多个可接受原始输入的机器,所以加一个超级源点,向这些点连边;同理,加一个超级汇点,让所有可以有最终输出的机器连向它。
机器的有流量(效率)限制,由于限制在点上,所以把点拆成两个点:输入点和输出点,两个点之间连一条边,容量就是该机器的效率。
建好图后要备份一下图,跑完最大流后两个图的边比较得知哪些边是必须要加的。

Code

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 50, P = 10, Q = 10000;

int g[N+1<<1][N+1<<1]; // 邻接矩阵存图
int aug[N+1<<1], pre[N+1<<1];

int Edmonds_Karp(int s, int t)
{
    int flow = 0;
    while(1)
    {
        memset(aug, 0, sizeof aug);
        aug[s] = Q;
        queue<int> que;
        que.push(s);
        for(int tp; !que.empty(); que.pop())
        {
            tp = que.front();
            for(int i = 1; i <= t; ++i)
                if(!aug[i] && g[tp][i] > 0)
                {
                    pre[i] = tp;
                    aug[i] = min(aug[tp], g[tp][i]);
                    que.push(i);
                }
            if(aug[t])
                break;
        }
        if(!aug[t])
            break;
        for(int v = t; v != s; v = pre[v])
        {
            g[pre[v]][v] -= aug[t];
            g[v][pre[v]] += aug[t];
        }
        flow += aug[t];
    }
    return flow;
}

int q[N+1], s[N+1][P], d[N+1][P];
int G[N+1<<1][N+1<<1]; // 图 g 的备份
int from[N*N], to[N*N], cap[N*N]; // 必须建的边

int main()
{
    int p, n;
    scanf("%d%d", &p, &n);
    for(int i = 1; i <= n; ++i)
    {
        scanf("%d", q+i);
        for(int j = 0; j < p; ++j)
            scanf("%d", s[i]+j);
        for(int j = 0; j < p; ++j)
            scanf("%d", d[i]+j);
    }
    memset(g, 0, sizeof g);
    // 拆点 -> i 和 i+n 对应
    // i 是输入,i + n 是输出
    for(int i = 1; i <= n; ++i)
        g[i][i+n] = q[i];
    // 点间、与源点、与汇点连边
    for(int i = 1; i <= n; ++i)
    {
        // 与源点、汇点
        bool st = true, tm = true;
        for(int j = 0; j < p; ++j)
        {
            // 输入无 1 连源点
            st &= s[i][j] != 1;
            // 输出全 1 连汇点
            tm &= d[i][j] == 1;
        }
        if(st) // 源点到输入点
            g[0][i] = Q; // 0 是源点
        if(tm) // 输出点到汇点
            g[i+n][n<<1|1] = Q; // 2n+1 是汇点
        // 点间
        for(int j = 1; j <= n; ++j)
            if(i != j)
            {
                int f = Q;
                for(int k = 0; k < p; ++k)
                    if(d[i][k] + s[j][k] == 1) // 一个是 0 另一个是 1
                    {
                        f = 0; // 则不能连边
                        break;
                    }
                g[i+n][j] = f;
            }
    }
    memcpy(G, g, sizeof g); // 备份原图
    printf("%d ", Edmonds_Karp(0, n<<1|1));
    int cnt = 0;
    for(int i = 1; i <= n; ++i)
        for(int j = 1; j <= n; ++j)
            if(g[i+n][j] < G[i+n][j]) // 上限变小 -> 此路有被使用过
            {
                from[cnt] = i;
                to[cnt] = j;
                cap[cnt++] = G[i+n][j] - g[i+n][j];
            }
    printf("%d\n", cnt);
    while(cnt--)
        printf("%d %d %d\n", from[cnt], to[cnt], cap[cnt]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值