[NOI2012][bzoj2879] 美食节 [费用流+动态加边]

Posted Orion545

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[NOI2012][bzoj2879] 美食节 [费用流+动态加边]相关的知识,希望对你有一定的参考价值。

题面

传送门

思路

先看看这道题

修车

仔细理解一下,这两道题是不是一样的?

这道题的不同之处

但是有一个区别:本题中每一种车有多个需求,但是这个好办,连边的时候容量涨成$p\lbrack i\rbrack$就好了

但是还有一个区别:数据量变大了-_-

这直接导致了费用流裸做,TLE60分,因为有超过6e6条边

我们得想个办法改进一下

观察可得,这道题里,按照我们的模型,最多出现800条增广路,而且每次增广都是一的流量

也就是说我们实际上跑800次spfa即可

但是spfa和边唯一相关,我们全建好的图中6e6*800*k肯定会T

那我们就要想个办法优化边数

优化

我们观察发现,第一次spfa得出的最短路肯定是某人倒数第一个修某车某厨师倒数第一个做某菜,因为倒数第一个肯定比倒数第二个距离短

那么我们可以在一开始建图的时候,只把所有“倒数第一个做的菜”的那些边加上

一旦一条增广路被用掉了(也就是一个厨师-做菜顺序二元组$\left(j,k\right)$被用掉了),那么我们就把所有代表二元组$\left(j,k+1\right)$加上去(一共有n条),再跑spfa

这样我们图中的总边数不会超过$n\ast\sum_{i=1}^n p\lbrack i\rbrack$

也就是总时间在$O\left(np^2\ast k\right)$左右,k是spfa常数

这样就可以过了

Code

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define inf 1e9
#define id(i,j) ((i-1)*p+j+n)
#define left(x) ((x-n-1)/p+1)
#define right(x) ((x-n-1)%p+1)
using namespace std;
inline int read(){
    int re=0,flag=1;char ch=getchar();
    while(ch>'9'||ch<'0'){
        if(ch=='-') flag=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9') re=(re<<1)+(re<<3)+ch-'0',ch=getchar();
    return re*flag;
} 
int n,m,cnt=-1,first[100010],dis[100010],vis[100010],pre[100010],limit[100010];
struct edge{
    int to,next,w,cap;
}a[10000010];
inline void add(int u,int v,int w,int cap){
    a[++cnt]=(edge){v,first[u],w,cap};first[u]=cnt;
    a[++cnt]=(edge){u,first[v],-w,0};first[v]=cnt;
}
int q[200010],ans,cost[50][110],p;
bool spfa(int s,int t){
    int head=0,tail=1,u,v,w,i;
    memset(dis,-1,sizeof(dis));memset(vis,0,sizeof(vis));
    memset(pre,-1,sizeof(pre));memset(limit,0,sizeof(limit));
    q[0]=s;vis[s]=1;dis[s]=0;limit[s]=inf;
    while(head<tail){
        u=q[head++];vis[u]=0;
        for(i=first[u];~i;i=a[i].next){
            v=a[i].to;w=a[i].w;
            if(a[i].cap&&((dis[v]==-1)||(dis[v]>dis[u]+w))){
                dis[v]=dis[u]+w;
                pre[v]=i;limit[v]=min(limit[u],a[i].cap);
                if(!vis[v]) q[tail++]=v,vis[v]=1;
            }
        }
    }
    return ~dis[t];
}
void mcmf(int s,int t){
    int u,i;
    while(spfa(s,t)){//这里最多sigma(p[i])次
        for(u=t;~pre[u];u=a[pre[u]^1].to){
            a[pre[u]].cap-=limit[t];a[pre[u]^1].cap+=limit[t];
            ans+=limit[t]*a[pre[u]].w;
        }//跑完一次更新答案
        u=a[pre[t]^1].to;//u就是当前消耗的二元组,u+1就是下一个二元组
        add(u+1,t,0,1);
        for(i=1;i<=n;i++) add(i,u+1,cost[i][left(u+1)]*right(u+1),1);//加上对应的下一组边
    }
}
int main(){
    memset(first,-1,sizeof(first));int i,j,t1;
    n=read();m=read();
    for(i=1;i<=n;i++){
        t1=read();p+=t1;
        add(0,i,0,t1);
    }
    for(i=1;i<=n;i++){
        for(j=1;j<=m;j++){
            cost[i][j]=read();
            add(i,id(j,1),cost[i][j],1);//初始边
        }
    }
    for(j=1;j<=m;j++) add(id(j,1),n+p*m+1,0,1);
    mcmf(0,n+p*m+1);
    cout<<ans<<endl;
}

以上是关于[NOI2012][bzoj2879] 美食节 [费用流+动态加边]的主要内容,如果未能解决你的问题,请参考以下文章

bzoj2879[Noi2012]美食节 费用流+动态加边

[BZOJ2879] [Noi2012] 美食节 (费用流 & 动态加边)

[NOI2012][bzoj2879] 美食节 [费用流+动态加边]

BZOJ-2879美食节 最小费用最大流 + 动态建图

C++之路进阶——最小费用最大流(善意的投票)

BZOJ 2878: [Noi2012]迷失游乐园( 树形dp )