[BZOJ1070]修车

·· / ·– ·· ·-·· ·-·· / ·–· · ·-· ··· ·· ··· - / ··- -· - ·· ·-·· / ·· / ·– ·· -·
题目来源:http://www.lydsy.com/JudgeOnline/problem.php?id=1070
题目有一点要注意的就是等待时间包括之前的人修车的时间。
应该是比较容易看出费用流的,修车时间就是费用,而每个“修车的人”就被抽象成了流。
本来是写的增广一次就累加那个修车师傅的总时间的,但因为实际增广是可能会“收回”流量,所以并不能输出正解。
这里引用一下http://travisbraps.top/Newt/442

    费用流,把每个技术人员拆点,表示其修倒数第i辆车,这样后面的人等的时间就可以简单得出了

    从原点连到每个拆烂了的工作人员(orz),费用为0,流量为1

    从每辆车连到汇点,费用为0,流量为1

    对于对应的拆烂的工作人员和车连边,流量为1,费用为t[k][i]*j //倒数第j辆

这里有两个费用流中非常有意思的思想。
1、第一个是把选择的情况都化成独立的边。这样就可以在增广时“收回”流而不影响正确答案的得出。因为费用流优先走cost小的边,在选cost大的边之前一定已经走了小边。
2、第二个是倒着想。设每个人的修车时间为a[i],等待时间为s[i],则s[n]=a[1]+a[2]+…+a[n],每一个师傅那里总待的时间=s[1]+s[2]+s[3]+…+s[n]。但是如果不考虑这个人前面有多少人修车了(实际也没必要考虑),而只考虑这个人自己修车对后面人的影响,则计算会容易得多。直接考虑这个人是倒数第几个修车的,那么这个人只会影响到他后面的修车的时间,这个人对全局的影响就是t[k][i]*j

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>
const int N=1010,M=100020,inf=0x3fffffff;
using namespace std;
int ver[M<<1],nxt[M<<1],e[M<<1],w[M<<1],c[M<<1],head[N],tot=1;
int dis[N],pos[N],pre[N],a[N][N],map[N][N];
bool vis[N];
int m,n,s,t,cost=0,cnt=0;
double ans=0;
void add (int u,int v,int w,int cost) {
    ver[++tot]=v;e[tot]=w;c[tot]=cost;nxt[tot]=head[u];head[u]=tot;
    ver[++tot]=u;e[tot]=0;c[tot]=-cost;nxt[tot]=head[v];head[v]=tot;
}
bool spfa () {
    memset(pre,-1,sizeof(pre));
    for (int i=0;i<N;i++) dis[i]=inf;
    memset(pos,0,sizeof(pos));
    memset(vis,0,sizeof(vis));
    queue<int> q; int now,v;
    q.push(s); vis[s]=1; dis[s]=0; pre[s]=s;//
    while (!q.empty()) {
        now=q.front(); q.pop();
        for (int i=head[now];i;i=nxt[i]) {
            v=ver[i]; 
            if (e[i]&&dis[v]>dis[now]+c[i]) {
                dis[v]=dis[now]+c[i];
                pos[v]=i; pre[v]=now;
                if (!vis[v]) q.push(v),vis[v]=1;//
            }
        }
        vis[now]=0;
    }
    return pre[t]!=-1;//
}
void mcf () {
    int aug;
    while (spfa()) {
        aug=inf;
        for (int i=t;i!=s;i=pre[i]) aug=min(aug,e[pos[i]]);
        cost+=aug*dis[t];
        for (int i=t;i!=s;i=pre[i])
            e[pos[i]]-=aug,e[pos[i]^1]+=aug;
    }
}
int main () {
    scanf("%d%d",&m,&n);
    s=0,t=n+m*n+1;
    for (int i=1;i<=m;i++) 
        for (int j=1;j<=n;j++) 
            map[i][j]=++cnt;
    for (int i=1;i<=n;i++)
        for (int j=1;j<=m;j++)
            scanf("%d",&a[i][j]);
    for (int i=1;i<=n;i++)
        add(s,i,1,0);
    for (int i=1;i<=n;i++)
        for (int j=1;j<=m;j++)
            for (int k=1;k<=n;k++)
                add(i,n+map[j][k],1,a[i][j]*k);
    for (int i=1;i<=m;i++)
        for (int j=1;j<=n;j++)
            add(n+map[i][j],t,1,0);
    mcf();
    ans=cost;
    printf("%.2lf",ans/n);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值