NOI2012 美食节 动态加边维护费用流

题目大意

现在有 N 种人,每种人有Pi个,还有 M 个厨师,第j个厨师给第 i 种人做菜需要时间Ti,j,并且同一时间一个厨师只能做一道菜。每个人的权值是这个人等待的时间。问所有人的权值和最小是多少。

N40
M100
Pi800

解题思路

Sum=Pi
我们考虑用网络流做这题,我们把第 i 个厨师差拆成Sum个点,第 k 个点表示这个厨师做的倒数第k道菜,每个点向每个人 j 连一条流量为1,费用为 Tj,ik ,向 T 连一条流量为1,费用为 0 的边。每个人想S连一条流量为 Pi ,费用为 0 的边。显然跑一次最小费用最大流的费用就是答案。

但是这样肯定会超时!因为边太多了。所以我们可以考虑动态加边,对于每个厨师,做完代表倒数k个菜的点才把代表倒数 k+1 个菜的点加进去即动态加边,就能大大减少网络流遍历边的时间,并且我们不用但新是否会流了代表倒数第 k 个菜的点,但是没流代表倒数第k1个菜的点,因为我们肯定会优先流费用小的边。

程序

//YxuanwKeith
#include <cstring>
#include <cstdio>
#include <algorithm>

using namespace std;

const int MAXN = 100000;

int All, S, T, N, M, Min, P[50], t[50][1000], D[MAXN];
int tot, Last[MAXN], Go[MAXN * 2], Len[MAXN * 2], Next[MAXN * 2], Cost[MAXN * 2], Dis[MAXN];
bool Flag[MAXN], Bz[MAXN];

void Open(int u, int v, int flow, int cost) {
    Next[++ tot] = Last[u], Last[u] = tot, Go[tot] = v, Len[tot] = flow, Cost[tot] = cost;
}

void Link(int u, int v, int flow, int cost) {
    Open(u, v, flow, cost);
    Open(v, u, 0, -cost);
}

void Add(int Num, int Cnt) {
    int Ord = (Num - 1) * All + Cnt;
    if (Cnt > All || Flag[Ord]) return;
    Flag[Ord] = 1;
    Link(S, Ord, 1, 0);
    for (int i = 1; i <= N; i ++) Link(Ord, All * M + i, 1, t[i][Num] * Cnt);
}

int Dfs(int Now, int flow) {
    if (Now == T) {
        Min += Dis[T] * flow;
        return flow;
    }
    int Use = 0;
    Bz[Now] = 1;
    for (int p = Last[Now]; p; p = Next[p]) {
        int v = Go[p];
        if (Dis[v] == Dis[Now] + Cost[p] && Len[p] > 0 && !Bz[v]) {
            int t = Dfs(v, min(flow - Use, Len[p]));
            Len[p] -= t, Len[p ^ 1] += t, Use += t;
            if (Use == flow) {
                if (Now <= All * M && Now != S) Add((Now - 1) / All + 1,(Now - 1) % All + 2);
                return Use;
            }
        }
    }
    return Use;
}

void Go_Flow() {
    int Cnt = 0;
    while (1 == 1) {
        memset(Bz, 0, sizeof Bz), memset(Dis, 60, sizeof Dis);
        int Inf = Dis[0];
        int l = 0, r = 1;
        D[1] = S, Dis[S] = 0;
        while (l != r) {
            l = (l + 1) % MAXN;
            int Now = D[l];
            for (int p = Last[Now]; p; p = Next[p]) {
                int v = Go[p];
                if (Len[p] && Dis[v] > Dis[Now] + Cost[p]) {
                    Dis[v] = Dis[Now] + Cost[p];
                    if (!Bz[v]) {
                        Bz[v] = 1;
                        r = (r + 1) % MAXN;
                        D[r] = v;
                    }
                }
            }
            Bz[Now] = 0;
        }
        if (Dis[T] == Inf) return;
        Dfs(S, Inf);
    }
}

int main() {
    scanf("%d%d", &N, &M);
    for (int i = 1; i <= N; i ++) {
        scanf("%d", &P[i]);
        All += P[i];
    } 

    S = 0, T = All * M + N + 1;
    tot = 1;
    for (int i = 1; i <= N; i ++) {
        Link(All * M + i, T, P[i], 0);
        for (int j = 1; j <= M; j ++) scanf("%d", &t[i][j]);
    }
    for (int i = 1; i <= M; i ++) Add(i, 1);

    Go_Flow();
    printf("%d\n", Min);
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值