洛谷P2053:[SCOI2007]修车 (网络流)

题目传送门:https://www.luogu.org/problemnew/show/P2053


题目分析:这题是我今早上数学课的时候,花10分钟突然想到的……

它要我们求平均值最小,其实就是求和最小。先简化问题:假设每一辆车车主的等待时间都等于修他自己车的时间,要怎么构图?很明显就是下面这样(虽然贪心就可以做了,但我们还是将图画出来):

其中逗号左边的数代表流量上限,右边的数代表费用。左边的一列点是车辆,右边的一列点是维修人员。

现在回到原问题:每一个车主的等待时间等于修他自己和前面的车的时间。
假设一个人修了3辆车,从左到右编号为3,5,6,记r[i]表示修第i辆车的时间,那么等待时间总和为r[3]+(r[3]+r[5])+(r[3]+r[5]+r[6])=r[3]*3+r[5]*2+r[6]。我们发现对于一个维修人员,他修的最后一辆车的时间对答案的贡献要乘以1,修倒数第二辆车的时间要乘以2贡献给答案……以此类推。同时也意味着,对于每一个人,时间乘以1后贡献答案的只能有1辆车,时间乘以2后贡献答案的也只能有1辆车……于是我们将一个人拆成m个点,将每一辆车的时间乘以1~m连向这m个点,同时限制每个点流向t的流量只能为1:

然后问题就完美解决辣!


CODE:

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;

const int maxn=10000;
const int maxm=1000000;
const int oo=1000000000;

struct edge
{
    int obj,cap,cost;
    edge *Next,*rev;
} e[maxm];
edge *head[maxn];
edge *nhead[maxn];
int cur=-1;

int dis[maxn];
bool vis[maxn];
int que[maxn];
int he,ta;

int n,m,t;

void Add(int x,int y,int f,int c)
{
    cur++;
    e[cur].obj=y;
    e[cur].cap=f;
    e[cur].cost=c;
    e[cur].rev=&e[cur+1];
    e[cur].Next=head[x];
    head[x]=e+cur;

    cur++;
    e[cur].obj=x;
    e[cur].cap=0;
    e[cur].cost=-c;
    e[cur].rev=&e[cur-1];
    e[cur].Next=head[y];
    head[y]=e+cur;
}

void Release(int x,int y,int len)
{
    if (dis[y]<=dis[x]+len) return;
    dis[y]=dis[x]+len;
    if (!vis[y]) vis[y]=true,ta=(ta+1)%maxn,que[ta]=y;
}

bool SPFA()
{
    for (int i=0; i<=t; i++) nhead[i]=head[i],dis[i]=oo;
    dis[0]=0;
    vis[0]=true;

    he=0,ta=1;
    que[1]=0;
    while (he!=ta)
    {
        he=(he+1)%maxn;
        int node=que[he];
        for (edge *p=head[node]; p; p=p->Next)
            if (p->cap) Release(node,p->obj,p->cost);
        vis[node]=false;

        int nhe=(he+1)%maxn;
        if ( nhe!=ta && dis[ que[nhe] ]>dis[ que[ta] ] ) swap(que[nhe],que[ta]);
    }

    return (dis[t]<oo);
}

int Dfs(int node,int maxf)
{
    if ( node==t || !maxf ) return maxf;
    vis[node]=true;
    int nowf=0;
    for (edge *&p=nhead[node]; p; p=p->Next)
    {
        int to=p->obj;
        if ( p->cap && dis[to]==dis[node]+p->cost && !vis[to] )
        {
            int d=Dfs(to, min(maxf,p->cap) );
            p->cap-=d;
            p->rev->cap+=d;
            maxf-=d;
            nowf+=d;
            if (!maxf) break;
        }
    }
    vis[node]=false;
    return nowf;
}

int Min_cost_flow()
{
    int temp=0;
    while ( SPFA() ) temp+=( dis[t]*Dfs(0,maxm) );
    return temp;
}

int main()
{
    freopen("2053.in","r",stdin);
    freopen("2053.out","w",stdout);

    scanf("%d%d",&n,&m);

    t=(n+1)*m+1;
    for (int i=0; i<=t; i++) head[i]=NULL;
    for (int i=1; i<=m; i++) Add(0,i,1,0);
    for (int i=m+1; i<t; i++) Add(i,t,1,0);
    for (int i=1; i<=m; i++)
        for (int j=1; j<=n; j++)
        {
            int v;
            scanf("%d",&v);
            for (int k=0; k<m; k++) Add(i,m+k*n+j,1,v*(k+1));
        }

    double ans=Min_cost_flow();
    ans/=(double)m;
    printf("%.2lf\n",ans);

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值