Jzoj P4371 作业分配___费用流+动态开边

题目大意:

n n 科作业,第i科作业有 ai a i 份,有 m m 个人,第j个人只做其中任意 bj b j 份作业,我们知道第 j j 个人做一份第i科作业的时间是 ci,j c i , j
问所有人做作业的总时间的和最小值是多少。

ni=1ai=mj=1bj ∑ i = 1 n a i = ∑ j = 1 m b j
n<=200m<=200ai,bi<=10000sum(ai)<=1000000 n <= 200 , m <= 200 , a i , b i <= 10000 , s u m ( a i ) <= 1000000

分析:

很容易想到费用流,
源点 S S 向所有科目连边,费用为0,流量为 a[i] a [ i ]
每科向每人连边,费用为 c[i][j] c [ i ] [ j ] ,流量为 a[i] a [ i ]
每人向汇点 T T 连边,费用为0,流量为 b[j] b [ j ]
但是会超时,
所以我们考虑动态加边,
在之前加入的最短边满流后才加的边
也就是说我们一开始对第 i i 科只向做这科需要最短时间的人j连边
j j 到汇点T的边满流时,我们就加入做这科需要时间次短的人,以此类推,当然当下一次加入的人 k k 到汇点T的边依然满流的时候, i i k的边也不能忽略不加,因为网络流中是有反悔边的,所以这条边可能会使结果更优。

代码:

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<queue>
#define inf 2333333
#define M 100005
#define N 505

using namespace std;

struct edge { int To, num, cost, nxt;}e[M*2];
struct Node { int num, id;}c[N][N];
int incf[N], tot[N], flow[N], dis[N], pre[N], ls[N], v[N], a[N], b[N], n, m, maxflow, ans, cnt = 1;

void Addedge(int u, int v, int w, int f) {
    e[++cnt].To = v; e[cnt].num = w; e[cnt].cost =  f; e[cnt].nxt = ls[u]; ls[u] = cnt; 
    e[++cnt].To = u; e[cnt].num = 0; e[cnt].cost = -f; e[cnt].nxt = ls[v]; ls[v] = cnt;
}

bool cmp(Node aa, Node bb) {
    return aa.num < bb.num;
}

bool spfa(int s, int t) {
    for (int i = s; i <= t; i++)
         v[i] = 0, dis[i] = inf;
    queue <int> Q; Q.push(s); 
    v[s] = 1; dis[s] = 0;
    incf[s] = inf;
    while (Q.size()) {
        int u = Q.front(); Q.pop();
        for (int i = ls[u]; i; i = e[i].nxt)
             if (e[i].num && dis[e[i].To] > dis[u] + e[i].cost) {
                dis[e[i].To] = dis[u] + e[i].cost;
                incf[e[i].To] = min(incf[u], e[i].num);
                pre[e[i].To] = i;
                if (!v[e[i].To]) {
                     Q.push(e[i].To);
                     v[e[i].To] = 1;
                }
            }
        v[u] = 0;
    }
    return dis[t] != inf;
}

void update(int s, int t) {
    int x = t;
    while (x != s){
          int i = pre[x];
          e[i].num -= incf[t];
          e[i^1].num += incf[t];
          x = e[i^1].To;
    }
    maxflow += incf[t];
    ans += dis[t]*incf[t];
}

void edmonds_karp(int s, int t){
    while (spfa(s, t)) {
           update(s, t);
           for (int i = 1; i <= n; i++) {
                while (!e[flow[c[i][tot[i]].id]].num && tot[i] < m) 
                        tot[i]++, Addedge(i, n + c[i][tot[i]].id, a[i], c[i][tot[i]].num);
           }
    }
}

int main() {
    scanf("%d%d", &n, &m);
    int S = 0, T = n + m + 1;
    for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
    for (int i = 1; i <= m; i++) scanf("%d", &b[i]);
    for (int i = 1; i <= n; i++) {
         for (int j = 1; j <= m; j++) scanf("%d", &c[i][j].num), c[i][j].id = j;
         sort(c[i]+1, c[i]+m+1, cmp); tot[i] = 1;
         Addedge(i, n+c[i][1].id, a[i], c[i][1].num);
    }
    for (int i = 1; i <= n; i++) Addedge(S, i, a[i], 0);
    for (int i = 1; i <= m; i++) Addedge(n+i, T, b[i], 0), flow[i] = cnt - 1;
    edmonds_karp(S, T);
    printf("%d\n", ans);
    return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值