【NOI2012】美食节

费用流,动态加点

题目大意:

有 $n$ 种菜 $m$ 个厨师。第 $j$ 个厨师做第 $i$ 种菜要花 $a_{i,j}$ 的时间。

现在点第 $i$ 种菜的有 $p_i$ 人。一个厨师同时只能做一道菜,多个厨师可以分别同时做菜。

问每个人需要等待的时间的总和最少是多少。

题解:

网络流。

厨师 $i$ 做第 $j$ 种菜,做的是他要做的倒数第 $k$ 道,那么有 $k$ 个人要等这道菜做完,产生的等待时间为 $a_{j,i}\times k$。

那我们把厨师拆成 $k$ 个点,每个点再分别向点每种菜的人连边,费用为 $k\times a_{j,i}$。

跑最小费用最大流即可。

由于 $a_{i,j}\geq0$,所以每次显然会先增广 $k$ 小的边($0$ 的话没有影响不用管)。

但是这样边的个数会很多,过不去。

实际上,$\sum p_i$ 不是很大,增广的路径总数不多。也就是说有很多边是没用的。

再考虑刚刚那个东西,我们知道它会先增广 $k$ 小的边。

那我们每次增广一条路径,只有当前 $k$ 被增广的条件下,再新增 $k+1$ 这个点。

这样就大大减少了边数,可以通过。

Code:

#include<iostream>
#include<utility>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
const int N=1e4,T=9997,inf=0x3f3f3f3f;
struct edge{
    int to,nxt,cap,cost;
}e[2333333];
int n,m,p[N],head[N],cnt=1,nod;
int P[N],ck[888][888],a[888][888];
bool vis[N];
int dis[N],pre_e[N],aa[N];
long long ans;
pair<int,int>info[N];
inline void addedge(int u,int v,int cap,int cost){
    e[++cnt]=(edge){v,head[u],cap,cost},head[u]=cnt;
    e[++cnt]=(edge){u,head[v],0,-cost},head[v]=cnt;
}
void new_(int id,int k){
    ck[id][k]=++nod;
    addedge(0,nod,1,0);
    info[nod]=make_pair(id,k);
    for(int i=1;i<=n;++i)
    addedge(nod,P[i],1,k*a[id][i]);
}
queue<int>q;
bool MCMF(){
    memset(dis,0x3f,sizeof dis);
    memset(aa,0,sizeof aa);
    memset(pre_e,0,sizeof pre_e);
    aa[0]=inf;
    dis[0]=0;
    for(q.push(0);!q.empty();){
        int u=q.front();q.pop();
        vis[u]=0;
        for(int i=head[u];i;i=e[i].nxt)
        if(e[i].cap&&dis[e[i].to]>dis[u]+e[i].cost){
            dis[e[i].to]=dis[u]+e[i].cost;
            aa[e[i].to]=min(e[i].cap,aa[u]);
            pre_e[e[i].to]=i;
            if(!vis[e[i].to])vis[e[i].to]=1,q.push(e[i].to);
        }
    }
    if(dis[T]>1e9)return 0;
    ans+=dis[T]*(long long)aa[T];
    for(int x=T;x;){
        int i=pre_e[x],pr=e[i^1].to;
        e[i].cap-=aa[T],e[i^1].cap+=aa[T];
        if(info[pr]!=make_pair(0,0)){
            int id=info[pr].first,k=info[pr].second;
            if(!ck[id][k+1])new_(id,k+1);
        }
        x=pr;
    }
    return 1;
}
int main(){
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    cin>>n>>m;
    for(int i=1;i<=n;++i){
        cin>>p[i];
        P[i]=++nod;
        addedge(P[i],T,p[i],0);
    }
    for(int i=1;i<=n;++i)
    for(int j=1;j<=m;++j)
    cin>>a[j][i];
    for(int i=1;i<=m;++i)
    new_(i,1);
    while(MCMF());
    cout<<ans<<'\n';
    return 0;
}