洛谷 P2050 [NOI2012]美食节 网络流 最小费用最大流 Dinic+Spfa

题目链接:

https://www.luogu.com.cn/problem/P2050

思路来源博客:

https://www.luogu.com.cn/blog/litble-blog/ti-xie-p2050-mei-si-jie-fei-yong-liu

这个博客真心很棒,建议复习再看

算法:1:最小费用最大流 Dinic+Spfa

思路:

1:这题就是P2053修车https://blog.csdn.net/aiwo1376301646/article/details/104313451的数据加强版,那么建图方式也差不多

2:对于每个菜建立一个点,源点向其连一条流量为需求量费用为0的边

3:然后再建一层点,分别表示第j个厨师做第倒数i道菜。向汇点连一条流量为1费用为0的边

4:把每一个厨师拆为(一共要做的菜的总盘数sum个)而不是菜的种类n个,所以一共有m*sum个厨师节点

5:假设有一个点表示第j个厨师做第倒数k道菜,那么对于菜i,向其连一条流量为1,费用为k×a(i,j)的边。这表示第j个厨师做的倒数第k道菜是菜i,那么就要做a(i,j)这么长的时间,有k个人要等这么长的时间

6:意会一下可以发现,这个模型能解决“同时做”问题

样例图解:

优化:

1:由于此题数据量很大,把所有边连完后再跑费用流是一定会TLE的(60分)

2:由于我们跑一次spfa只能找出一次增广路,所以我们可以暂时不连不需要的边。一开始,我们把所有厨师做倒数第1道菜与所有菜连好,然后找一条增广路,这条增广路上一定经过了一个点,表示第j个厨师做倒数第1道菜,于是我们添加点(第j个厨师做倒数第2道菜),与汇点和所有菜连边,以此类推

3:意会一下可以发现,这样每次spfa的时候,需要的边都被连上了

图解:

1:第一步(一开始)红色线

2:第一步之后 添加第二个蓝点,需要添加的边(绿色)

代码:

一:加边函数addedge()没有一次就把正反边都包括,这样不如一次就包括省事

#include <bits/stdc++.h>

using namespace std;
const int maxn=8e4+4e1+2,maxm=6e6+5e5+8e1+1;
int m,n,s,t,a[41][101],tot=1,head[maxn],dis[maxn],flow[maxn],pre[maxn],last[maxn],maxflow,mincost,p[41],sum;
int cook[maxn],dish[maxn];
bool vis[maxn];
queue<int>q;

struct edge
{
    int to,next,w,dis;
}e[maxm];

void addedge(int x,int y,int w,int dis)
{
    e[++tot].to=y;e[tot].w=w;e[tot].dis=dis;e[tot].next=head[x];head[x]=tot;
}

bool spfa()
{
    memset(dis,0x7f,sizeof(dis));
    memset(flow,0x7f,sizeof(flow));
    memset(vis,0,sizeof(vis));
    q.push(s);vis[s]=1;dis[s]=0;pre[t]=-1;
    while(!q.empty())
    {
        int now=q.front();q.pop();
        vis[now]=0;
        for(int i=head[now];i;i=e[i].next)
        {
            int y=e[i].to;
            if(e[i].w>0&&dis[y]>dis[now]+e[i].dis)
            {
                dis[y]=dis[now]+e[i].dis;
                pre[y]=now;
                last[y]=i;
                flow[y]=min(flow[now],e[i].w);
                if(!vis[y])
                {
                    q.push(y);
                    vis[y]=1;
                }
            }
        }
    }
    return pre[t]!=-1;
}

void dfs()
{
    int now=t;
    maxflow+=flow[t];
    mincost+=flow[t]*dis[t];
    while(now!=s)
    {
        e[last[now]].w-=flow[t];
        e[last[now]^1].w+=flow[t];
        now=pre[now];
    }
}

int main()
{
    ios::sync_with_stdio(0);
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;i++)scanf("%d",&p[i]),sum+=p[i];
    s=0,t=m*sum+n+1;
    for(int i=1;i<=n;i++)addedge(s,i+sum*m,p[i],0),addedge(i+sum*m,s,0,0);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
        {
           scanf("%d",&a[i][j]);
           addedge(i+sum*m,sum*(j-1)+1,1,a[i][j]),addedge(sum*(j-1)+1,i+sum*m,0,-a[i][j]);
        }
    for(int i=1;i<=m;i++)addedge((i-1)*sum+1,t,1,0),addedge(t,(i-1)*sum+1,0,0);
    for(int i=1;i<=m;i++)
        for(int j=1;j<=sum;j++)
    {
        int temp=(i-1)*sum+j;
        dish[temp]=j,cook[temp]=i;
    }
    while(spfa())
    {
        dfs();
        int temp=e[last[t]^1].to;
        addedge(temp+1,t,1,0),addedge(t,temp+1,0,0);
        for(int i=1;i<=n;i++)
            addedge(i+m*sum,temp+1,1,a[i][cook[temp+1]]*dish[temp+1]),
            addedge(temp+1,i+m*sum,0,-a[i][cook[temp+1]]*dish[temp+1]);
    }
    printf("%d\n",mincost);
    return 0;
}

二:加边函数addedge()一次就把正反边都包括

#include <bits/stdc++.h>

using namespace std;
const int maxn=8e4+4e1+2,maxm=6e6+5e5+8e1+1,inf=0x7fffffff;
int m,n,s,t,a[41][101],tot=1,head[maxn],dis[maxn],flow[maxn],pre[maxn],last[maxn],maxflow,mincost,p[41],sum;
int cook[maxn],dish[maxn];
bool vis[maxn];
queue<int>q;

struct edge
{
    int to,next,w,dis;
}e[maxm];

void addedge(int x,int y,int w,int dis)
{
    e[++tot].to=y;e[tot].w=w;e[tot].dis=dis;e[tot].next=head[x];head[x]=tot;
    e[++tot].to=x;e[tot].w=0;e[tot].dis=-dis;e[tot].next=head[y];head[y]=tot;
}

bool spfa()
{
    memset(dis,0x7f,sizeof(dis));
    memset(last,0,sizeof(last));
    memset(vis,0,sizeof(vis));
    flow[s]=inf;
    q.push(s);vis[s]=1;dis[s]=0;pre[t]=-1;
    while(!q.empty())
    {
        int now=q.front();q.pop();
        vis[now]=0;
        for(int i=head[now];i;i=e[i].next)
        {
            int y=e[i].to;
            if(e[i].w>0&&dis[y]>dis[now]+e[i].dis)
            {
                dis[y]=dis[now]+e[i].dis;
                pre[y]=now;
                last[y]=i;
                flow[y]=min(flow[now],e[i].w);
                if(!vis[y])
                {
                    q.push(y);
                    vis[y]=1;
                }
            }
        }
    }
    if(pre[t]==-1)return 0;
    int now=t;
    maxflow+=flow[t];
    mincost+=flow[t]*dis[t];
    while(now!=s)
    {
        e[last[now]].w-=flow[t];
        e[last[now]^1].w+=flow[t];
        now=pre[now];
    }
    return 1;
}

int main()
{
    ios::sync_with_stdio(0);
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;i++)scanf("%d",&p[i]),sum+=p[i];
    s=0,t=m*sum+n+1;
    for(int i=1;i<=n;i++)addedge(s,i+sum*m,p[i],0);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
        {
           scanf("%d",&a[i][j]);
           addedge(i+sum*m,sum*(j-1)+1,1,a[i][j]);
        }
    for(int i=1;i<=m;i++)addedge((i-1)*sum+1,t,1,0);
    for(int i=1;i<=m;i++)
        for(int j=1;j<=sum;j++)
    {
        int temp=(i-1)*sum+j;
        dish[temp]=j,cook[temp]=i;
    }
    while(spfa())
    {
        int temp=e[last[t]^1].to;
        addedge(temp+1,t,1,0);
        for(int i=1;i<=n;i++)
            addedge(i+m*sum,temp+1,1,a[i][cook[temp+1]]*dish[temp+1]);
    }
    printf("%d\n",mincost);
    return 0;
}
发布了164 篇原创文章 · 获赞 82 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/aiwo1376301646/article/details/104320228
今日推荐