理发时间 2017信息学夏令营第三场

NKOJ4231 理发时间

时间限制 : 1000MS 空间限制 : 165536 KB

问题描述

何老板开了一家理发店,店里有m名发型师。 今天,信竞班的n名同学一起同时来到何老板的店,每个同学都要理发,并且每个同学的发型要求都不相同。
每个发型师处理不同的发型所需时间可能不同。何老板想要给大家安排一个合理的理发顺序,使得大家等候的总时间最少。一个同学的等待时间是指从他到店开始到理发完毕所用的时间。

输入格式

第一行有两个整数m和n。 接下来一个n*m的整数矩阵,其中第i行第j列的数字表示第i号同学的发型由第j号理发师来处理所需的时间。

输出格式

一个整数,表示等候的最少总时间。

样例输入 1

2 2
3 2
1 4

样例输出 1

3

样例输入 2

3 9
42 2 53
16 66 94
37 55 99
77 79 11
9 2 95
19 49 10
5 19 91
36 14 95
100 61 54

样例输出 2

214

提示

对于100%的数据:2<=M<=10,1<=N<=100,1<=单个人理发的时间<=1000


费用流,关键都是构图。

一开始容易想到费用提前计算,稍作分析后发现行不通。但是思路可以借鉴:
关注当前的操作会对后面造成什么影响!

正向想难以操作,就不妨反向考虑:假设我们已经求出了最优解了,关注最优解有什么特征。

假设我们已经安排好了最优的理发顺序,考虑其中一个理发师j。最后一个在那里理发的人i对其他在他那里理发的人没有影响,对答案的贡献就是time[i][j]。倒数第二个在那里理发的人k会影响到倒数第一个人的等待时间,加上自己的等待时间,对答案的贡献就是2 × time[k][j]。以此类推,可以得到关键的结论:

倒数第a个在j理发师处理发的人i对答案的贡献为: a × time[i][j]。

那么构图就比较显然了:把每个理发师拆成N × M个点,每个理发的人向这N × M个点连边,费用就是这样做对答案的贡献,容量为1。再从源点向每个理发的人连上费用为0,容量为1的边。从每个理发师拆成的点连向汇点,费用为0,容量为1。跑一次最小费用最大流即可。

构图时给点编号不要把自己弄晕。

#include<stdio.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<queue>
#define ll long long
#define Min(x,y) ((x<y)?(x):(y))
const int MAXN=1234,MAXM=234567;
const ll inf=1e18;
using namespace std;
ll Cost,Map[105][15];
int N,M,S,T;

int en[MAXM],las[MAXN],nex[MAXM],G[MAXM],tot=1;
ll len[MAXM];
void ADD(int x,int y,ll z,int w)
{
    en[++tot]=y;
    nex[tot]=las[x];
    las[x]=tot;
    len[tot]=z;
    G[tot]=w;
}
bool mark[MAXN];
ll dis[MAXN];
int pre[MAXN],pp[MAXN];
bool SPFA()
{
    int i,x,y;
    queue<int>Q;

    for(i=S;i<=T;i++)dis[i]=inf;
    dis[S]=0;
    Q.push(S);
    while(Q.size())
    {
        x=Q.front();mark[x]=false;Q.pop();
        for(i=las[x];i;i=nex[i])
        {
            y=en[i];
            if(G[i]>0&&dis[y]>dis[x]+len[i])
            {
                dis[y]=dis[x]+len[i];
                pre[y]=x;
                pp[y]=i;
                if(!mark[y])mark[y]=true,Q.push(y);
            }
        }
    }
    if(dis[T]<inf)return true;
    return false;
}

void AddFlow()
{
    int i;ll flow=inf;
    for(i=T;i!=S;i=pre[i])flow=Min(flow,G[pp[i]]);
    Cost+=1ll*dis[T]*flow;
    for(i=T;i!=S;i=pre[i])G[pp[i]]-=flow,G[pp[i]^1]+=flow;
}

int main()
{
    int i,j,k;

    scanf("%d%d",&M,&N);
    for(i=1;i<=N;i++)
        for(j=1;j<=M;j++)scanf("%d",&Map[i][j]);

    T=N*M+N+1;

    for(i=1;i<=N;i++)ADD(S,i,0,1),ADD(i,S,0,0);
    for(i=1;i<=N;i++)
        for(j=1;j<=M;j++)
            for(k=1;k<=N;k++)
            {
                ADD(i,j*N+k,Map[i][j]*k,1);
                ADD(j*N+k,i,-Map[i][j]*k,0);
            }
    for(j=1;j<=M;j++)
        for(k=1;k<=N;k++)
        {
            ADD(j*N+k,T,0,1);
            ADD(T,j*N+k,0,0);
        }
    while(SPFA())AddFlow();
    printf("%lld",Cost);
}
/*

编号方法: 
1.源点:0
2.顾客:1~N
3.理发师: 
    第i个理发师拆成的点: N*i+1~N*(i+1) 
    表示第i个理发师给倒数第k个人理发的点:N*i+k 
4.汇点:N*(M+1)+1 

*/

做完这道题之后想起了另外一道题NKOJ2425 体检,觉得类型有一点点相似,就写了下。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值