题目链接

这是一道很不错的费用流好题,建图的思想很是巧妙

我们把每个工人拆成\(n\)个点,表示当前工人在\(n\)个不同的时间段,那么\(m\)个工人就是\(n*m\)个点,然后把这些点向汇点连一条费用为0边权为1的边,也就是同一时段一个工人只能维修一辆车。对于每辆车,我们先从汇点连出一条费用为0边权为1的边,表示每辆车只会被一个工人在一个时段维修一次。然后对于每辆车,我们向之前的\(n*m\)个点连边,边权为1,费用为工人维修这辆车的时间*这是工人的第几个时间段。然后跑费用流,最后答案除以\(n\),保留两位小数就可以了。

但是这样为什么是对的呢?

对于同一辆车,同一个工人,时间段越靠后,产生的权值也就越大。那么流量肯定会优先从靠前的时间段流出。那么如果一个工人要维修多辆车,他们所用的时间段一定是相邻的,并且在最前面。那么工人要维修的时间段最靠后的车,产生的权值是“工人维修这辆车的时间*这是工人的第几个时间段”,那么也就相当于维修这辆车使当前工人维修的所有车的车主产生的等待时间之和,换句话说这里的时间段越早,修的就越晚,时间段最晚的,实际上是当前工人第一个维修的车辆。所以这个时间段其实是指当前工人维修的倒数第几辆车。这样的话,也就能够看出为什么这样建图是正确的了。

下放代码

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cctype>
#include<queue>
#define ll long long
#define gc getchar
#define maxn 1005
#define maxm 100005
using namespace std;

inline ll read(){
    ll a=0;int f=0;char p=gc();
    while(!isdigit(p)){f|=p=='-';p=gc();}
    while(isdigit(p)){a=(a<<3)+(a<<1)+(p^48);p=gc();}
    return f?-a:a;
}int n,m,S,T,ans,a[maxn][10];

struct ahaha{
    int f,w,to,next;
}e[maxm<<1];int tot,head[maxn];
inline void add(int u,int v,int w,int f){
    e[tot]={f,w,v,head[u]};head[u]=tot++;
}
inline void Add(int u,int v,int w,int f){
    add(u,v,w,f);add(v,u,0,-f);
}

deque<int>q;
int b[maxn],d[maxn],fl[maxn],la[maxn];
int spfa(){memset(fl,63,sizeof fl);
    memset(d,63,sizeof d);d[S]=0;la[T]=-1;q.push_back(S);
    while(!q.empty()){
        int u=q.front();q.pop_front();b[u]=0;
        for(int i=head[u];~i;i=e[i].next){
            int v=e[i].to;if(e[i].w<=0||d[v]<=d[u]+e[i].f)continue;
            d[v]=d[u]+e[i].f;la[v]=i;
            fl[v]=min(fl[u],e[i].w);
            if(b[v])continue;b[v]=1;
            if(q.empty()||d[v]<d[q.front()])q.push_front(v);
            else q.push_back(v);
        }
    }return ~la[T];
}

int main(){memset(head,-1,sizeof head);
    m=read();n=read();T=n+n*m+1;
    for(int i=1;i<=n;++i)
        for(int j=1;j<=m;++j)
            a[i][j]=read();
    for(int i=1;i<=n;++i){
        Add(S,i,1,0);int p=n;
        for(int j=1;j<=m;++j)
            for(int k=1;k<=n;++k)
                Add(i,++p,1,a[i][j]*k);  //流量流经这条边表示第i辆车是第j个工人维修的倒数第k辆车
    }int p=n;
    for(int i=1;i<=n;++i)
        for(int j=1;j<=m;++j)
            Add(++p,T,1,0);
    while(spfa()){
        ans+=fl[T]*d[T];
        int now=T;
        while(now!=S){
            e[la[now]].w-=fl[T];
            e[la[now]^1].w+=fl[T];
            now=e[la[now]^1].to;
        }
    }
    printf("%.2f\n",1.0*ans/n);
    return 0;
}