bzoj2879 [NOI2012]美食节(费用流)

题目链接

分析:
看完题面,我就想到了这道题
实际上,这道题的建图就是这样:
这里写图片描述

写完之后 —> 60
(一定要算准空间)

边的数量比较多,所以如果一口气把所有的边都连上的话,时间是承受不了的

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

这样每次spfa的时候,需要的边都被连上了

tip

点的编号要好好计算

空间一定要算对(会T)

结点个数:n+m*p
边数:2*(n+(n+1)*m*p)

//这里写代码片
#include<cstdio>
#include<cstring>
#include<iostream>
#include<queue>
#define ll long long

using namespace std;

const int INF=0x33333333;
const int N=8000000;
struct node{
    int x,y,v,c,nxt;
};
node way[N];
int st[100000],s,t,dis[100000],pre[100000],a[43][105],tot=-1,C[43];
int n,m,tt,pos,wh;
bool in[100000];

void add(int u,int w,int z,int cc)
{
    tot++;
    way[tot].x=u;way[tot].y=w;way[tot].v=z;way[tot].c=cc;way[tot].nxt=st[u];st[u]=tot;
    tot++;
    way[tot].x=w;way[tot].y=u;way[tot].v=0;way[tot].c=-cc;way[tot].nxt=st[w];st[w]=tot;
}

int spfa(int s,int t)
{
    memset(in,0,sizeof(in));
    memset(dis,0x33,sizeof(dis));
    dis[s]=0; in[s]=1;
    queue<int> Q;
    Q.push(s);
    while (!Q.empty())
    {
        int now=Q.front(); Q.pop();
        for (int i=st[now];i!=-1;i=way[i].nxt)
            if (way[i].v&&way[i].c+dis[now]<dis[way[i].y])
            {
                dis[way[i].y]=way[i].c+dis[now];
                pre[way[i].y]=i;
                if (!in[way[i].y])
                {
                    Q.push(way[i].y);
                    in[way[i].y]=1;
                }
            }
        in[now]=0;
    }
    return dis[t]!=INF;
}

int fan(int bh)
{
    bh-=n;
    wh=(bh%tt)+1;
    pos=bh/tt; 
    if (bh%tt!=0) pos++;
}

ll doit()
{
    ll ans=0;

    spfa(s,t);
    int sum=INF;
    for (int i=t;i!=s;i=way[pre[i]].x)
        sum=min(sum,way[pre[i]].v);
    ans+=(ll)sum*dis[t];
    for (int i=t;i!=s;i=way[pre[i]].x)
        way[pre[i]].v-=sum,
        way[pre[i]^1].v+=sum;

    fan(way[pre[t]].x);

    return ans;
}

ll solve()
{
    ll ans=0;
    s=0; 
    int cnt=0,cnt2=0;
    for (int i=1;i<=n;i++)
        add(s,i,C[i],0);
    t=tt*m+n+1;
    for (int i=1;i<=m;i++) 
        add((i-1)*tt+1+n,t,1,0);

    for (int i=1;i<=n;i++)          //所有菜向每一个厨师的倒数第一道连边 
        for (int j=1;j<=m;j++)
        {
            int num=n+(j-1)*tt+1;
            add(i,num,1,a[i][j]);       
        }  

    ans+=doit();
    for (int k=1;k<tt;k++)         //第pos个厨师的倒数第wh道菜 与汇点和所有菜连边 
    {
        for (int i=1;i<=n;i++)
            add(i,(pos-1)*tt+wh+n,1,wh*a[i][pos]);      
        add((pos-1)*tt+wh+n,t,1,0);
        ans+=doit();
    }
    return ans;
}

int main()
{
    tt=0;
    memset(st,-1,sizeof(st));
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++) scanf("%d",&C[i]),tt+=C[i];
    for (int i=1;i<=n;i++)
        for (int j=1;j<=m;j++)
            scanf("%d",&a[i][j]);
    printf("%lld",solve());
    return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值