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 体检,觉得类型有一点点相似,就写了下。