【最小费用流】【NOI2008】志愿者招募

题目描述:

【问题描述】
申奥成功后,布布经过不懈努力,终于成为奥组委下属公司人力资源部门的主管。布布刚上任就遇到了一个难题:为即将启动的奥运新项目招募一批短期志愿者。经过估算,这个项目需要 N 天才能完成,其中第 i 天至少需要 Ai 个人。布布通过了解得知,一共有 M 类志愿者可以招募。其中第 i 类可以从第 Si 天工作到第 Ti 天,招募费用是每人 Ci 元。新官上任三把火,为了出色地完成自己的工作,布布希望用尽量少的费用招募足够的志愿者,但这并不是他的特长!于是布布找到了你,希望你帮他设计一种最优的招募方案。
【输入格式】
输入文件 employee.in 的第一行包含两个整数 N, M,表示完成项目的天数和可以招募的志愿者的种类。
接下来的一行中包含 N 个非负整数,表示每天至少需要的志愿者人数。接下来的 M 行中每行包含三个整数 Si, Ti, Ci,含义如上文所述。为了方便起见,我们可以认为每类志愿者的数量都是无限多的。
【输出格式】
输入文件 employee.out 中仅包含一个整数,表示你所设计的最优方案的总费用。
【输入样例】
33
234
122
235
332
【输出样例】
14
【样例说明】
招募 3 名第一类志愿者和 4 名第三类志愿者。
【数据规模和约定】
30%的数据中,1 ≤ N, M ≤ 10,1 ≤ Ai ≤ 10;
100%的数据中,1 ≤ N ≤ 1000,1 ≤ M ≤ 10000,题目中其他所涉及的数据均不超过 231-1。
最开始看到这个题,就想尽一切办法建立网络流的模型,比如 :建立超级源,源到每天的流量即为每天的需求量,希望就此作为最小割,再建立一系列约束,使得每个类型的志愿者到每天的流量恰好使得每天的流量都达到饱和。

但始终都不能满足要求。

看了题解之后,换了一个角度思考,既然不能让通过每天的流量为实际的绝对流量,那么就考虑使用相对流量。
具体方法如下:(除特殊说明,流量都为无限,费用都为0。)
第一步:添加超级源S和超级汇T,若第i天的需求量比第(i - 1)天的需求量大,那么从S处补充(有流量上限);否则让多出的流量流到T(有流量上限)。(特别地,设第(n + 1)天的的需求量为0。)
第二步:对于每一类志愿者,设工作的区间为[Li, Ri],那么从Li向Ri + 1连一条边,费用为该志愿者的耗费。
第三步:从每一个i + 1都到i连一条边。

求最小费用流,即为题目所求。

还可以用线性规划的思想来理解。

就拿样例来说,可以得出以下不等式组:

添加非负项yi(i = 0, 1, ...),得到方程组:

用每一项减去上一项,得:

于是发现方程组的每一项都是由一些变量和常量构成,并且常量之和为零。这里就有点像网络流模型中除源点和汇点外所有点流量平衡。
那么把每个方程式看作图中的每一个点,添加超级源,添加超级汇,建图出来就和上面的结果类似了。

代码:

/****************************\
 * @prob: NOI2008 employee  *
 * @auth: Wang Junji        *
 * @stat: Accepted.         *
 * @date: May. 17th, 2012   *
 * @memo: 最小费用流          *
\****************************/
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cstring>
#include <string>

const int maxN = 1010, INF = 0x3f3f3f3f;
struct Edge
{
	int u, v, f, d; Edge *next, *back; Edge() {}
	Edge(int u, int v, int f, int d, Edge *next):
		u(u), v(v), f(f), d(d), next(next) {}
} *edge[maxN], *pre[maxN];
int max_flow[maxN], n, m, S, T, ans;

inline int getint()
{
	int res = 0; char tmp;
	while (!isdigit(tmp = getchar()));
	do res = (res << 3) + (res << 1) + tmp - '0';
	while (isdigit(tmp = getchar()));
	return res;
}

inline void Ins(int u, int v, int f, int d)
{
	edge[u] = new Edge(u, v, f, d, edge[u]);
	edge[v] = new Edge(v, u, 0, -d, edge[v]);
	edge[u] -> back = edge[v];
	edge[v] -> back = edge[u];
	return;
}

inline bool Spfa()
{
	static const int SIZE = 0xffff;
	static int q[SIZE + 1], dist[maxN];
	static bool marked[maxN]; int f = 0, r = 0; 
	pre[T] = NULL; max_flow[S] = INF; max_flow[T] = 0;
	memset(dist, 0x3f, sizeof dist); dist[S] = 0;
	marked[q[r++] = S] = 1;
	while (f - r)
	{
		int u = q[f++], v; f &= SIZE; marked[u] = 0;
		for (Edge *p = edge[u]; p; p = p -> next)
		if (p -> f > 0 && dist[u] + p -> d < dist[v = p -> v])
		{
			dist[v] = dist[u] + p -> d; pre[v] = p;
			max_flow[v] = std::min(max_flow[u], p -> f);
			if (!marked[v]) marked[q[r++] = v] = 1, r &= SIZE;
		}
	}
	ans += max_flow[T] * dist[T];
	for (Edge *p = pre[T]; p; p = pre[p -> u])
		p -> f -= max_flow[T], p -> back -> f += max_flow[T];
	return (bool)pre[T];
}

int main()
{
	freopen("employee.in", "r", stdin);
	freopen("employee.out", "w", stdout);
	n = getint(); m = getint();
	S = n + 2, T = n + 3 int Last = 0;;
	for (int i = 1; i < n + 1; ++i)
	{
		int ths = getint();
		if (ths > Last) Ins(S, i, ths - Last, 0);
		if (ths < Last) Ins(i, T, Last - ths, 0);
		Ins(i + 1, i, INF, 0); Last = ths;
	}
	Ins(n + 1, T, Last, 0);
	while (m--)
	{
		int L = getint(), R = getint(), c = getint();
		Ins(L, R + 1, INF, c);
	}
	while (Spfa()); printf("%d\n", ans); return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值