洛谷P1251 餐巾计划问题[最小费用最大流]

中文题干
写了挺久的,后来wr了好几次,发现建图建错了。又交了好几次 还是wr。后来发现 我tm把S和题目里面的s搞在一起了
自闭在厕所。

先来讲讲怎么建图。
网络流的建图一直是一个很难的问题。
我是这么想的,每天有两件事情要做

  1. 早上拿来新毛巾 —> 来源: 买 快洗部,慢洗部,昨天留下来没用的(我之前就是把这个忘记了- -)
  2. 晚上把脏了的毛巾拿去洗。 当然该洗多少,这是跑网络流的问题。

先说为什么要有昨天留下来的把。
刚开始我想的很简单,觉得我需要多少买多少,然后尽可能的拿去洗,这样子我不就可以少买了吗?
但是,有可能会多的,因为要是今天能获得的慢洗部和快洗部的餐巾比今天要用的多怎么办?难道不要了吗,所以要存起来。

然后我们已经把两件事情分开了,那么就可以分成两类集合,然后就是之间的一些边的关系了,再加上我们的源点和汇点。

我们先考虑什么东西和源点连接,是早上还是晚上?
其实都可以。因为早上的三个来源,使得早上一定会和晚上和源点有边存在。而晚上所得到的脏毛巾的量是相同的,而且不分来源。所以为了方便 我们就把源点和晚上连一条边,流量是每天的餐巾量。这里就是我们的第一类边了
那么 我们的早上就和汇点连接,流量就是每天需要的餐巾。这里就是我们的第二类边了
然后是源点和早上连接的边,流量是INF 因为我早上想买几条就买几条,多了的我还可以存起来(虽然没意义) 这里就是我们的第三类边了
然后是早上和晚上的关系。这个关系和我们的快洗部慢洗部有关系,流量是无穷大,在这些边之前的边是我们脏毛巾的量,当跑最大流的时候,这个边的流量是不会大于那个值得,所以就偷个懒不管他是多少给他赋成无穷大。这里就是我们的第四、五类边了
最后就是早上和早上之间的边了,我们把相邻的两天连起来,流量也是无穷大,不解释了。让边从昨天指向今天。这里就是我们的第六类边了

建完图跑就行了。。
我也没学过什么什么模型,也不知道这是什么模型,只是按照自己感觉来的。。。

struct edge {
    ll u, v, c, f, next;
};

edge e[10000010];
int head[len];//len = 5010.
ll dis[len];
int inq[len];
int pre[len];
int day[len];
ll p, m, f, n, s, hcnt;
int main(){
    Init();
    int N; cin >> N;
    for (int i=1; i<=N; i++) cin >> day[i];
    cin >> p >> m >> f >> n >> s;

    int S = 0, t = 2 * N + 1;
    for (int i=1; i<N; i++) adde(i+N,i+1+N,0,INF);

    for (int i=1; i<=N; i++) adde(S, i, 0, day[i]);
    for (int i=1; i<=N; i++) adde(N+i, t, 0, day[i]);
    for (int i=1; i<=N; i++) adde(S, N+i, p, INF);
    for (int i=1; i<=N; i++) {
        if (i+N+m <= 2 * N) adde(i, i+N+m, f, INF);
        if (i+N+n <= 2 * N) adde(i, i+N+n, s, INF);
    }

//    for (int i=s; i<=t; i++){
//        for (int j=head[i]; ~j; j=e[j].next){
//            cout << e[j].u << " " <<e[j].v << " " << e[j].f << " " << e[j].c << endl;
//        }
//    }
    dfs(S, t);
    return 0;
}
void Init(){
    memset(head, -1, sizeof (head));
    memset(dis, INF, sizeof (dis));
    memset(pre, -1, sizeof (pre));
    memset(inq, 0, sizeof (inq));
    hcnt = 0;
}

void adde(int u, int v, int c, int f){
    e[hcnt].u = u; e[hcnt].v = v; e[hcnt].c = c; e[hcnt].f = f;
    e[hcnt].next = head[u], head[u] = hcnt++;
    e[hcnt].u = v; e[hcnt].v = u; e[hcnt].c = -c; e[hcnt].f = 0;
    e[hcnt].next = head[v], head[v] = hcnt++;
}

bool spfa(int s, int t){
    memset(pre, -1, sizeof (pre));
    memset(inq, 0, sizeof (inq));
    for (int i=s; i<=t; i++) dis[i] = INF;
    queue <int> q;
    q.push(s); dis[s] = 0; inq[s] ++;
    while (!q.empty()){
        int h = q.front(); q.pop();

        for (int i=head[h]; ~i; i=e[i].next){
            int v = e[i].v;
            if (e[i].f && dis[v] > dis[h] + e[i].c){
                dis[v] = dis[h] + e[i].c;
                pre[v] = i;
                if (!inq[v]){
                    inq[v] ++;
                    q.push(v);
                }
            }
        }
        inq[h] = 0;
    }
    if (pre[t] == -1) return false;
    else return true;
}

void dfs(int s, int t){
    ll cost = 0;
    while (spfa(s, t)){
        ll minf = INF;

        for (int i=pre[t]; ~i; i=pre[e[i].u])
            if (minf > e[i].f) minf = e[i].f;

        for (int i=pre[t]; ~i; i=pre[e[i].u]){
            e[i].f -= minf;
            e[i^1].f += minf;
            cost += minf * e[i].c;
        }
    }
    cout << cost << endl;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值