【最大流+最短路+二分】POJ-2112 Optimal Milking

34 篇文章 0 订阅
30 篇文章 0 订阅
Optimal Milking
Time Limit: 2000MS Memory Limit: 30000K
   
Case Time Limit: 1000MS

Description

FJ has moved his K (1 <= K <= 30) milking machines out into the cow pastures among the C (1 <= C <= 200) cows. A set of paths of various lengths runs among the cows and the milking machines. The milking machine locations are named by ID numbers 1..K; the cow locations are named by ID numbers K+1..K+C. 

Each milking point can "process" at most M (1 <= M <= 15) cows each day. 

Write a program to find an assignment for each cow to some milking machine so that the distance the furthest-walking cow travels is minimized (and, of course, the milking machines are not overutilized). At least one legal assignment is possible for all input data sets. Cows can traverse several paths on the way to their milking machine. 

Input

* Line 1: A single line with three space-separated integers: K, C, and M. 

* Lines 2.. ...: Each of these K+C lines of K+C space-separated integers describes the distances between pairs of various entities. The input forms a symmetric matrix. Line 2 tells the distances from milking machine 1 to each of the other entities; line 3 tells the distances from machine 2 to each of the other entities, and so on. Distances of entities directly connected by a path are positive integers no larger than 200. Entities not directly connected by a path have a distance of 0. The distance from an entity to itself (i.e., all numbers on the diagonal) is also given as 0. To keep the input lines of reasonable length, when K+C > 15, a row is broken into successive lines of 15 numbers and a potentially shorter line to finish up a row. Each new row begins on its own line. 

Output

A single line with a single integer that is the minimum possible total distance for the furthest walking cow. 

Sample Input

2 3 2
0 3 2 1 1
3 0 3 2 0
2 3 0 1 0
1 2 1 0 2
1 0 0 2 0

Sample Output

2
————————————————————清晰的分割线————————————————————
前言:建图功力是无数错误累积出来的。
思路:首先看到了最小的最大距离,又知道距离的上限,而且该距离具备单调性,因此二分答案——最大距离。
然后奶牛和奶牛、奶牛和挤奶器之间有着不同的权值,因此需要跑一遍最短路,为每头奶牛安排最佳路线。
P.S. 注意边权为0代表不可走!!
二分答案的check函数,注意到每个挤奶器有挤奶限制,奶牛总数又是不变的,因此诉诸于网络流。通过Dinic,判断最大流是否等于奶牛总数。至于边,假如某奶牛到某挤奶器之间的距离超过了最大距离,那么该边不存在。至此我认为图完整了。
因此之前我是这样建图的:(注意这是错误的)
每个挤奶器作为一个顶点,从源点向每个挤奶器之间连边,如果某奶牛可达该挤奶器,则容量++,最后是挤奶器和汇点之间连边,容量为挤奶器的限制。
这张图错在哪里呢?
注意到从汇点出发的奶牛数总和可能超过真正的奶牛数。
也就是说,从源点出发的某头奶牛,如果两个挤奶器都可以走,它就出现了分身,走了两个路线。
该数量关系没有把握好。要限制从源点出发的奶牛总数!
从源点向每头奶牛添加一条容量1的边,这样就限制了每个奶牛只可能走一条路线。然后奶牛向挤奶器连接边。
P.S. 把握好边的总数。应该是12060+。
代码如下:
/*
ID: j.sure.1
PROG:
LANG: C++
*/
/****************************************/
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <stack>
#include <queue>
#include <vector>
#include <map>
#include <string>
#include <climits>
#include <iostream>
#define INF 0x3f3f3f3f
using namespace std;
/****************************************/
const int N = 333, M = 22222, MAXN = 333;
int n, mac, cow, lim, G[MAXN][MAXN];
struct Node {
	int u, v, cap;
	int next;
}edge[M];
int tot, head[N], lev[N], cur[N], q[N], s[N];
int S, T;

void init()
{
	tot = 0;
	memset(head, -1, sizeof(head));
}

void add(int u, int v, int c)
{
	edge[tot].u = u; edge[tot].v = v; edge[tot].cap = c;
	edge[tot].next = head[u]; head[u] = tot++;
}

bool bfs()
{
	int fron = 0, rear = 0;
	memset(lev, -1, sizeof(lev));
	lev[S] = 0;
	q[rear++] = S;
	while(fron < rear) {
		int u = q[fron%N]; fron++;
		for(int i = head[u]; i != -1; i = edge[i].next) {
			int v = edge[i].v;
			if(edge[i].cap && lev[v] == -1) {
				lev[v] = lev[u] + 1;
				q[rear%N] = v; rear++;
				if(v == T) return true;
			}
		}
	}
	return false;
}

void build(int maxi)
{
	init();
	for(int i = mac+1; i <= n; i++) {
		add(S, i, 1); add(i, S, 0);
	}
	for(int i = 1; i <= mac; i++) {
		add(i, T, lim); add(T, i, 0);
	}
	for(int i = mac+1; i <= n; i++) {
		for(int j = 1; j <= mac; j++) if(G[i][j] <= maxi) {
			add(i, j, 1); add(j, i, 0);
		}
	}
}

bool Dinic(int maxi)
{
	int ret = 0;
	build(maxi);//建图
	while(bfs()) {
		memcpy(cur, head, sizeof(cur));
		int u = S, top = 0;
		while(1) {
			if(u == T) {
				int mini = INF, loc;
				for(int i = 0; i < top; i++) {
					if(mini > edge[s[i]].cap) {
						mini = edge[s[i]].cap;
						loc = i;
					}
				}//找到路径上最小容量
				for(int i = 0; i < top; i++) {
					edge[s[i]].cap -= mini;
					edge[s[i]^1].cap += mini;
				}//修改容量
				ret += mini;
				top = loc;//回溯
				u = edge[s[top]].u;
			}
			int &i = cur[u];
			for(; i != -1; i = edge[i].next) {
				int v = edge[i].v;
				if(edge[i].cap && lev[v] == lev[u] + 1) break;//找允许弧
			}
			if(i != -1) {
				s[top++] = i;
				u = edge[i].v;
			}
			else {
				if(!top) break;
				lev[u] = -1;
				u = edge[s[--top]].u;
			}
		}
	}
	return ret == cow;
}

void Floyd()
{
	for(int k = 1; k <= n; k++) {
		for(int i = 1; i <= n; i++)  {
			for(int j = 1; j <= n; j++)  {
				G[i][j] = min(G[i][j], G[i][k] + G[k][j]);
			}
		}
	}
}

int bin_S(int maxi)
{
	int low = 0, high = maxi;
	while(low < high) {
		int mid = (low + high) >> 1;
		if(Dinic(mid)) high = mid;
		else low = mid+1;
	}
	return high;
}

int main()
{
#ifdef J_Sure
//	freopen("000.in", "r", stdin);
//	freopen(".out", "w", stdout);
#endif
	scanf("%d%d%d", &mac, &cow, &lim);
	n = mac + cow;
	S = 0; T = n+1;
	int x;
	for(int i = 1; i <= n; i++) {
		for(int j = 1; j <= n; j++) {
			scanf("%d", &x);
			G[i][j] = x ? x : INF;
		}
	}
	Floyd();
	int maxi = 0;
	for(int i = mac + 1; i <= n; i++) {
		for(int j = 1; j <= mac; j++) {
			if(G[i][j] < INF)
				maxi = max(maxi, G[i][j]);//奶牛距离挤奶器的最短路
		}
	}
	printf("%d\n", bin_S(maxi));
	return 0;
}


POJ - 3616是一个题目,题目描述如下: 给定一组区间,每个区间有一个权重,要求选择一些区间,使得这些区间的右端点都小于等于k,并且权重之和最大。请问最大的权重和是多少? 解决这个问题的思路是使用动态规划。首先,将区间按照左端点从小到大进行排序。然后,定义一个dp数组,dp[i]表示右端点小于等于i的所有区间所能得到的最大权重。 接下来,遍历每一个区间,对于每个区间i,将dp[i]初始化为区间i的权重。然后,再遍历i之前的每个区间j,如果区间j的右端点小于等于k,并且区间j的权重加上区间i的权重大于dp[i],则更新dp[i]为dp[j]加上区间i的权重。 最后,遍历整个dp数组,找到最大的权重和,即为所求的答案。 下面是具体的代码实现: ```cpp #include <cstdio> #include <cstring> #include <algorithm> using namespace std; struct interval{ int start, end, weight; }; interval intervals[10005]; int dp[10005]; int n, m, k; bool compare(interval a, interval b) { if (a.start == b.start) { return a.end < b.end; } else { return a.start < b.start; } } int main() { while(~scanf("%d %d %d", &n, &m, &k)) { memset(dp, 0, sizeof dp); for (int i = 0; i < m; i++) { scanf("%d %d %d", &intervals[i].start, &intervals[i].end, &intervals[i].weight); } sort(intervals, intervals + m, compare); for (int i = 0; i < m; i++) { dp[i] = intervals[i].weight; for (int j = 0; j < i; j++) { if (intervals[j].end <= k && dp[j] + intervals[i].weight > dp[i]) { dp[i] = dp[j] + intervals[i].weight; } } } int maxWeight = 0; for (int i = 0; i < m; i++) { maxWeight = max(maxWeight, dp[i]); } printf("%d\n", maxWeight); } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值