【费用流丨二分图最佳匹配】 [SCOI 2007] bzoj1070 修车

1070: [SCOI2007]修车



Time Limit: 1 Sec  Memory Limit: 162 MB
Submit: 2554  Solved: 1009
[Submit][Status]

Description



同一时刻有N位车主带着他们的爱车来到了汽车维修中心。维修中心共有M位技术人员,不同的技术人员对不同的车进行维修所用的时间是不同的。现在需要安排这M位技术人员所维修的车及顺序,使得顾客平均等待的时间最小。 说明:顾客的等待时间是指从他把车送至维修中心到维修完毕所用的时间。


Input



第一行有两个m,n,表示技术人员数与顾客数。 接下来n行,每行m个整数。第i+1行第j个数表示第j位技术人员维修第i辆车需要用的时间T。


Output



最小平均等待时间,答案精确到小数点后2位。


Sample Input



2 2


3 2


1 4

Sample Output



1.50

HINT



数据范围: (2<=M<=9,1<=N<=60), (1<=T<=1000)


题解1:网络流


2007年的省选题QAQ网络流考的真是裸啊。。(好像那年考了两道网络流。。?)要是现在省选题这么简单就好惹…………


。。这题又被建图坑了。。。一!定!要!仔!细!看!读!入!啊!!


其实最开始看到这道题真没想到拆点。。但是不拆又真不会做 然后毕克大神讲了讲大概懂了。。然后又去姜神博客研究了下(窝需要理理思路T T)


首先我们肯定是求总时间然后除以车的数量嘛……


因为技术人员比车少 所以如果有人需要等前面的车修完了才能修的话那就得把前面那辆车的时间也加到这辆车的时间里(好绕 = =)


总之就是我们倒着考虑 对于每一辆车 考虑它后面有k-1辆车要等待它 那么所有人等待它的总时间加上修它自己的时间总和为k*w(w为单独修它的时间)


嗯所以我们把每个技术人员拆成SumCustomer个点 然后每个点和源点相连流量为1(因为一次只能修一辆车w)边权为0 再将每个点和每个customer连边 流量为1 边权为k*w(k为拆成的第几个点 w同上) 然后customer要和汇点连边 流量为1(只用修一次嘛) 边权为0


然后最小费用最大流照着敲上去就行了 QAQ 不存在无解的状态所以也不用特别考虑什么 = =


#include <cstdio>
#include <iostream> 
#include <cstring>
#include <queue>

using namespace std;

const int inf = 0x3f3f3f3f;

int SumC, SumW;
int map[105][105];
int minf;
int S, T;

struct ed
{
	int v, w, flow, next;
}e[105*105*10];

int head[105*105*5], k;

inline void adde(int u, int v, int f, int w)
{
	e[k] = (ed){v, w, f, head[u]};
	head[u] = k++;
	e[k] = (ed){u, -w, 0, head[v]};
	head[v] = k++;
}

inline void build_map()
{
	cin >> SumW >> SumC;
	
	for(int i = 1; i <= SumC; ++i)
	{
		for(int j = 1; j <= SumW; ++j)
		{
			scanf("%d", &map[j][i]);
		}
	}
	
	int cnt = SumC;
	memset(head, -1, sizeof(head));
	
	for(int i = 1; i <= SumW; ++i)
	{
		for(int k = 1; k <= SumC; ++k)
		{
			++cnt;
			for(int j = 1; j <= SumC; ++j)
			{
				adde(cnt, j, 1, map[i][j] * k);
			} 
		}
	}
	
	S = 0; T = cnt + 1;
	
	for(int i = 1; i <= SumC; ++i) adde(i, T, 1, 0);
	for(int i = SumC + 1; i <= cnt; ++i) adde(S, i, 1, 0);
}

bool vis[105*105*5];
int dis[105*105*5];
int pre[105*105*5];

queue <int> q;

bool spfa()
{
	memset(dis, 0x3f, sizeof(dis));
	minf = inf;
	
	dis[S] = 0;
	q.push(S);
	
	while(!q.empty())
	{
		int u = q.front(); q.pop();
		vis[u] = 0;
		
		for(int i = head[u]; ~i; i = e[i].next)
		{
			int v = e[i].v;
			
			if(e[i].flow && dis[v] > dis[u] + e[i].w)
			{
				pre[v] = i;
				dis[v] = dis[u] + e[i].w;
				minf = min(e[i].flow, minf);
				
				if(!vis[v])
				{
					vis[v] = 1;
					q.push(v);
				}
			}
		}
	}
	
	return dis[T] < inf;
}

int main()
{
	build_map();
	
	int ans = 0;
	while(spfa())
	{
		for(int i = T; i != S; i = e[pre[i] ^ 1].v)
		{
			e[pre[i]].flow -= minf;
			e[pre[i] ^ 1].flow += minf;
		}
		
		ans += minf * dis[T]; 
	}
	
	printf("%.2f\n", (double)ans / SumC);
	
	return 0;
}


题解2:二分图最佳匹配


orz看完美匹配的时候发现这题可以用二分图做。。。。。

然后我觉得应该写起来很容易嘛就打算写下试试。。然后果断发现我是想多了 = =

和网络流一样的拆点和连边 然后把所有顾客作为一个集合U worker拆出来的点作为一个集合V

然后求最佳匹配就行了(把U匹配完) 用优化后的KM算法

求最小的话把权值变为负就行了。。然后输出的时候再负回来。。。

 = =我不知道为什么二分图代码写的比网络流还长。。时间的话网络流820s 二分图68s


#include <cstdio>
#include <iostream>
#include <cstring> 

using namespace std;

const int P = 60*9+5;
const int inf = 0x3f3f3f3f;

int SumC, SumW, SumU, SumV;
int map[P][P], w[P][P];

int match[P];
int lu[P], lv[P], slack[P];
bool visu[P], visv[P]; 

inline void build_map()
{
	cin >> SumW >> SumC;
	SumU = SumC; SumV = SumW * SumC;
	
	for(int i = 1; i <= SumC; ++i)
	{
		for(int j = 1; j <= SumW; ++j)
		{
			scanf("%d", &map[j][i]);
		}
	}
	
	int cnt = 0;
	
	for(int i = 1; i <= SumW; ++i)
	{
		for(int k = 1; k <= SumC; ++k)
		{
			++cnt;
			for(int j = 1; j <= SumC; ++j)
			{
				w[j][cnt] = -map[i][j] * k;
			}
		}
	}
	
	/*for(int i = 1; i <= SumU; ++i)
	{
		for(int j = 1; j <= SumV; ++j)
		{
			printf("%d ", w[i][j]);
		}
		puts("");
	}*/
}

bool dfs(int u)
{
	visu[u] = 1;
	for(int v = 1; v <= SumV; ++v) if(!visv[v])
	{
		int t = lu[u] + lv[v] - w[u][v];
		if(t == 0)
		{
			visv[v] = 1;
			if(match[v] == -1 || dfs(match[v]))
			{
				match[v] = u;
				return true;
			}
		}
		else if(slack[v] > t) slack[v] = t;
	}
	return false;
}

inline void adjust()
{
	int d = inf;
	
	for(int v = 1; v <= SumV; ++v) 
		if(!visv[v] && d > slack[v]) d = slack[v]; 
	for(int u = 1; u <= SumU; ++u)
		if(visu[u]) lu[u] -= d;
	for(int v = 1; v <= SumV; ++v)
	{
		if(visv[v]) lv[v] += d;
		else slack[v] -= d;
	}
}

int main()
{
	build_map(); 
	
	memset(match, -1, sizeof(match));
	for(int i = 1; i <= SumU; ++i)
	{
		lu[i] = -inf;
		for(int j = 1; j <= SumV; ++j)
		{
			if(w[i][j] > lu[i]) lu[i] = w[i][j];
		}
	} 
	
	for(int u = 1; u <= SumU; ++u)
	{
		for(int v = 1; v <= SumV; ++v) slack[v] = inf;
		for( ; ; )
		{
			memset(visu, 0, sizeof(visu));
			memset(visv, 0, sizeof(visv));
			
			if(dfs(u)) break;
			else adjust();
		}
	}
	
	int res = 0;
	for(int v = 1; v <= SumV; ++v) if(match[v] != -1)
		res += w[match[v]][v];
		
	printf("%.2lf", (double)-res/SumC);
	
	return 0;
}



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值