[NOI 2008] 志愿者招募

从这道题学到一种新颖的建模方法。感觉和区间k覆盖的模型有点相似,先去做了poj 3680。那个问题我没能自己想出来,书上的解法很好,但是不能推广到本题。

看了题解,得知需要列出一些式子,用网络流流量平衡来建模。

以样例为例。设第i类志愿者招募x[i]人,则

x[1] >= A[1]

x[1]+x[2] >= A[2]

x[2]+x[3] >= A[3]

求min{C[1]*x[1]+C[2]*x[2]+C[3]*x[3]}。

这是一个线性规划问题。而某些特殊的线性规划可以用网络流解决,就像可以用最短路解决差分约束系统。

在不等号的右边添加d[i]>=0,使它们变成等式。

x[1] = A[1]+d[1] ......(a1)

x[1]+x[2] = A[2]+d[2] ......(a2)

x[2]+x[3] = A[3]+d[3] ......(a3)

就看到这里,后面的变形我想自己处理。

注意到每种志愿者总是覆盖一段区间,因此出现了许多重复的x。或许作差会有用。于是用后面的式子减去前一个,移向得

x[2]+A[1]+d[1] = A[2]+d[2] ......(b2)

x[3]+A[2]+d[2] = x[1]+A[3]+d[3] ......(b3)

式(a1)没有可减的,怎么办呢?出于美学考虑,我决定(a1)-(a3),

x[1]+A[3]+d[3] = x[2]+x[3]+A[1]+d[1] ......(b1)

很高兴地发现(b1)~(b3)满足每个变量在等式的左边和右边各出现一次。于是我就开始写了。

样例的输出为0。

怎么回事呢?画了图,发现这样建图输出不为0只能说明我的最小费用最大流算法写错了。前几天学习高斯消元法,不久便意识到究竟哪里出了错。方程组(a)和(b)并不等价。(a3)-(a1) = ((a2)-(a1))+((a3)-(a2)),(b1)不是独立的。

如何修正?

首先得分析,为什么相邻两式作差会导致每个变量只在左边和右边各出现一次。对于x[i],它第一次出现时,去减前面的,移向后它必然出现在等式左边;它第一次消失时,作差移向后出现在等式右边;中间,它被减掉。

第一天的x在第一天第一次出现,最后一天的x在最后一天的下一天消失。所以,(a1)两边同时减0,用0=0减a(3)的两边即可体现这种变化。

x[1] = A[1]+d[1] ......(b1')

A[3]+d[3] = x[2]+x[3] ......(b4)

原来的3个方程变成了4个,但这没什么要紧的。我们没有丢失信息。

AC,但是CodeVS上比大多数人的代码慢了1s多。

最小费用最大流的算法不够好?但不至于除了我以外大家都写高级算法吧。没有优化读入?加了反而慢了些。

参考hzwer的代码。做法基本相同,但他把方程两边的常数A消去了一边。这样做是正确的,这些边费用为0,本身就优先增广它们。

修改之后时间基本正常了。

某些线性规划问题可以归约到网络流解决。


#include <cstdio>
#include <algorithm>
#include <queue>
using namespace std;
typedef long long ll;
const int MAXN = 1000, MAXM = 10000, MAXE = (MAXN*2+1+MAXM)*2, MAXV = MAXN+3;
const ll INF = 1LL<<60;

int e_ptr = 2;
int fst[MAXV+5];

struct Edge {
	int v, next;
	ll c, w;
} E[MAXE+5];

inline void addEdge(int u, int v, ll c, ll w)
{
	E[e_ptr] = (Edge){v, fst[u], c, w}; fst[u] = e_ptr++;
	E[e_ptr] = (Edge){u, fst[v], 0, -w}; fst[v] = e_ptr++;
}

template <typename Tp>
inline void read(Tp& x)
{
	char ch = getchar();
	x = 0;
	while (ch<'0' || ch>'9')
		ch = getchar();
	while (ch>='0' && ch<='9') {
		x = x*10 + ch - '0';
		ch = getchar();
	}
}

namespace MCMF {
	ll d[MAXV+5];
	int p[MAXV+5];
	bool inq[MAXV+5];

	int SPFA(int s, int t, ll& cost)
	{
		queue<int> Q;
		for (int i = s+1; i <= t; ++i) {
			d[i] = INF;
			inq[i] = false;
		}

		d[s] = 0;
		inq[s] = true;
		Q.push(s);
		
		while (!Q.empty()) {
			int u = Q.front();
			Q.pop();
			inq[u] = false;
			for (int i = fst[u]; i; i = E[i].next) {
				int v = E[i].v;
				if (E[i].c > 0 && d[u] + E[i].w < d[v]) {
					d[v] = d[u] + E[i].w;
					p[v] = i;
					if (!inq[v]) {
						inq[v] = true;
						Q.push(v);
					}
				}
			}
		}
		
		if (d[t] == INF)
			return 0;

		ll f = INF;
		for (int u = t; u != s; u = E[p[u]^1].v)
			f = min(f, E[p[u]].c);
	
		for (int u = t; u != s; u = E[p[u]^1].v) {
			E[p[u]].c -= f;
			E[p[u]^1].c += f;
			cost += f * E[p[u]].w;
		}

		return f;
	}

	ll MCMF(int s, int t)
	{
		ll cost = 0;
		while (SPFA(s, t, cost))
			;
		return cost;
	}
};

int main()
{
	int N, M;
	read(N); read(M);

	ll a = 0, b;
	for (int i = 1; i <= N; ++i) {
		read(b);
		if (b > a)
			addEdge(i, N+2, b-a, 0);
		else if (b < a)
			addEdge(0, i, a-b, 0);
		addEdge(i, i+1, INF, 0);
		a = b;
	}
	addEdge(0, N+1, a, 0);

	for (int i = 1; i <= M; ++i) {
		int s, t;
		ll w;
		read(s); read(t); read(w);
		addEdge(t+1, s, INF, w);
	}
	printf("%lld", MCMF::MCMF(0, N+2));
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值