[NOI2012]美食节

Description
  链接扔上来~
  
Solution
  我就觉得是网络流!(我就是不会捉!
  这道题的弱化版好像别的题解都写了,什么[SDOI2007]修车吧,然后去看了一下,这道题目就在它的基础上在搞了个动态加边。
  首先来建图:
  1.把每种菜建个点,从 s s 连到它的费用为0,流量为 pi p i 的边。
  2.然后给每个厨师的每个炒菜的时刻建个点,注意厨师 a a 的第一个点叫做厨师a倒数第一个做的菜。把这些点跟 t t 连边,费用为0,流量为 1 1
  3.那么我们把菜向厨师的每个时刻点连边,第i个时刻就要把原费用乘 i i ,这些边流量显然都是1

  然后我们就 T T 飞起了,考虑有好多边不知道加了干嘛根本没有用。具体来说,我们每在MCMF中增广一次,就是暂时确定了一个厨师的某个时刻炒了什么菜。显然如果一个厨师的倒数第 i i 个时刻没被选,那么他的倒数第i+1个时刻显然不会下一次被选。于是我们每增广一次,就找出来那个被确定的厨师,把他的下一个时刻的点添加进去。注意最开始的时候要把所有厨师的倒数第一时刻的点放入。
  于是效率为 O(np2spfa) O ( n p 2 ∗ s p f a 常 数 )

Source

//2018-5-21
//miaowey
//
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define For(i, a, b) for(int i = (a); i <= (int)(b); ++i)

#define N (100000 + 5)
const int oo = 0x3f3f3f3f;

int n, m, sum, ans, p[N], a[45][105];

struct Edge{
    int from, to, cap, flow, cost;
};

namespace MCMF{
    bool inq[N];
    int s, t, mxf[N], mnc[N], pre[N];
    vector<Edge> edges;
    vector<int> G[N];

    void AddEdge(int u, int v, int w, int co){
        edges.pb((Edge){u, v, w, 0, co});
        edges.pb((Edge){v, u, 0, 0, -co});

        int siz = edges.size();
        G[u].pb(siz - 2); G[v].pb(siz - 1);
    }

    bool Spfa(){
        queue<int> q;
        For(i, s, t) mnc[i] = oo, mxf[i] = pre[i] = 0;
        mnc[s] = 0; mxf[s] = oo; inq[s] = true; q.push(s);

        while(!q.empty()){
            int now = q.front(); q.pop();
            inq[now] = false;

            For(i, 0, G[now].size() - 1){
                Edge &e = edges[G[now][i]];

                if(mnc[e.to] > mnc[now] + e.cost && e.cap > e.flow){
                    mnc[e.to] = mnc[now] + e.cost;
                    mxf[e.to] = min(mxf[now], e.cap - e.flow);
                    pre[e.to] = G[now][i];

                    if(!inq[e.to]){
                        inq[e.to] = true; q.push(e.to);
                    }
                }
            }
        }

        if(!mxf[t]) return false;
        ans += mxf[t] * mnc[t];

        int now = t;
        while(now != s){
            edges[pre[now]].flow += mxf[t];
            edges[pre[now] ^ 1].flow -= mxf[t];
            now = edges[pre[now]].from;
        }

        return true;
    }
};

using namespace MCMF;

void Solve(){
    while(Spfa()){
        int now = edges[pre[t]].from;

        AddEdge(now + 1, t, 1, 0);
        For(i, 1, n)
            AddEdge(i + sum * m, now + 1, 1, a[i][now / sum + 1] * (now % sum + 1));
    }

    printf("%d\n", ans);
}

int main(){
#ifndef ONLINE_JUDGE
    freopen("food.in", "r", stdin);
    freopen("food.out", "w", stdout);
#endif

    scanf("%d%d", &n, &m);
    For(i, 1, n) scanf("%d", &p[i]), sum += p[i];
    For(i, 1, n) For(j, 1, m) scanf("%d", &a[i][j]);

    s = 0, t = n + sum * m + 1;
    For(i, 1, n){
        AddEdge(s, i + sum * m, p[i], 0);
        For(j, 1, m) AddEdge(i + sum * m, (j - 1) * sum + 1, 1, a[i][j]);
    }
    For(j, 1, m) AddEdge((j - 1) * sum + 1, t, 1, 0);

    Solve();

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值