bzoj1061-[Noi2008]志愿者招募-单纯形 & 费用流

\(n\)天,\(m\)类志愿者,一个第\(i\)类志愿者可以从第\(s_i\)天工作到第\(t_i\)天,第\(i\)天工作的志愿者不少于\(b_i\)个。每一类志愿者都有单价\(c_i\),问满足要求的最小花费。

分析

它其实是这样一个问题:

\[\text{Minimize }CX \\ AX\ge B \\ X\ge 0 \\ A_{ij}=\begin{cases} 1 && s_i\le j\le t_i \\ 0 && \text{otherwise} \end{cases} \]

这是一个线性规划问题。

单纯形法

线性规划的一种通用解法是单纯形法,复杂度为指数级,但一般情况下(不去卡它)效率十分令人满意。

要使用单纯形法,我们需要把线性规划转化成一般形式:

\[\text{Maximize } CX \\ AX\le B \\ X\ge 0 \]

然而这里的问题是,我们的线性规划是一个最小化问题,用的都是大于号,不是标准形式。这时候需要用强对偶定理转化这个问题。

如果一个线性规划有最优解,那么它的对偶问题也有最优解,并且最优解相同。

一个标准型对偶问题如下:

\[\text{Minimize } B^TX \\ A^TX\ge C^T \\ X\ge 0 \]

单纯形方法的意义在于在\(m\)维空间中从初始点不断跳到一个相邻的更高点。

有一个需要注意的地方,例如我们要最大化\(CX\),那么我们其实是把它变成\(P=CX\),然后变成\(-CX+P=0\)来解,所以要取负号。

代码

#include<cstdio>
#include<cctype>
#include<cstring>
#include<algorithm>
using namespace std;
int read() {
	int x=0,f=1;
	char c=getchar();
	for (;!isdigit(c);c=getchar()) if (c=='-') f=-1;
	for (;isdigit(c);c=getchar()) x=x*10+c-'0';
	return x*f;
}
const int maxm=1e4+10;
const int maxn=1e3+10;
const double inf=1e300;
double a[maxm][maxn];
int n,m;
void Simplex() {
	bool flag;
	do {
		flag=false;
		for (int col=1;col<=n;++col) if (a[m+1][col]<0) {
			int row=0;
			double mi=inf;
			for (int r=1;r<=m;++r) if (a[r][col]>0){
				double p=a[r][n+1]/a[r][col];
				if (p<0) continue; else if (p<mi) mi=p,row=r;
			}
			if (row) flag=true; else continue;
			for (int i=1;i<=m+1;++i) if (i!=row && a[i][col]!=0) {
				double p=a[i][col]/a[row][col];
				for (int j=1;j<=n+1;++j) if (j==col) a[i][j]*=-1; else a[i][j]-=a[row][j]*p;
			}
			break;
		}
	} while (flag);
}
int main() {
#ifndef ONLINE_JUDGE
	freopen("test.in","r",stdin);
#endif
	n=read(),m=read();
	for (int i=1;i<=n;++i) a[m+1][i]=-read();
	for (int i=1;i<=m;++i) {
		int s=read(),t=read();
		a[i][n+1]=read();
		for (int j=s;j<=t;++j) a[i][j]=1;
	}
	Simplex();
	printf("%d\n",(int)a[m+1][n+1]);
	return 0;
}

网络流

其实可以发现,这个问题有一个非常特别的地方,就是变量是连续出现的。

每个不等式是一个大于等于的形式,所以我们给每个不等式减掉一个\(y_i\),使其变成等于。\(Y\ge 0\)

\(i\)天的等式我们用\(P_i\)表示,加上\(P_0=P_{n+1}=0\),可以发现,如果写出\(W_i=P_i-P_{i-1},i\in[1,n+1]\),那么这样的每个式子中每个\(x_i,y_i\)都只出现了一次,一次是加一次是减。我们把常数移到左边,那么所有等式的右边都是0,左边含有一些变量和一个常数,可能为正的或负的。

这很像一个网络流的流量平衡!

把每个\(W_i\)看成网络中的一个点,新加源点和汇点,如果\(W_i\)中一个变量是负的,\(W_j\)中一个变量是正的,那么我们连边\((i,j)\),容量为无穷大。如果这个变量是\(x\),那么费用为\(c_i\),否则费用为0。如何处理常数呢?我们发现,如果一个常数是正的,那么就相当于从源点流进来的流量,如果是负的那就是这个点流去汇点的流量。

这样我们就完成了每个点的流量平衡,而且一个很重要的性质就是正常数之和与负常数之和的绝对值是相等的(从相减的过程可以得出),所以如果我们对这个网络求最大流,能够流满,那么所有条件都可以得到满足。

又因为我们要求的是最小费用,所以最小费用最大流的费用就是答案啦。

代码

#include<cstdio>
#include<cctype>
#include<cstring>
#include<algorithm>
using namespace std;
int read() {
	int x=0,f=1;
	char c=getchar();
	for (;!isdigit(c);c=getchar()) if (c=='-') f=-1;
	for (;isdigit(c);c=getchar()) x=x*10+c-'0';
	return x*f;
}
const int maxn=1e3+10;
const int maxm=1e4+10;
const int maxb=(maxn+maxn+maxm)<<1;
const int inf=2147483647;
int n,m,need[maxn],que[maxb],ql,qr,d[maxn],from[maxn];
bool inq[maxn];
struct graph {
	struct edge {
		int u,v,w,cost,nxt;
	} e[maxb];
	int tot,h[maxn];
	graph ():tot(1) {}
	void add(int u,int v,int w,int cost=0) {
		e[++tot]=(edge){u,v,w,cost,h[u]};
		h[u]=tot;
		e[++tot]=(edge){v,u,0,-cost,h[v]};
		h[v]=tot;
	}
	bool spfa(int s,int t,int &flow,int &cost) {
		que[ql=qr=1]=s;
		memset(inq,0,sizeof inq);
		memset(from,0,sizeof from);
		inq[s]=true;
		fill(d+s,d+t+1,inf);
		d[s]=0;
		while (ql<=qr) {
			int x=que[ql++];
			for (int i=h[x],v=e[i].v;i;i=e[i].nxt,v=e[i].v) if (e[i].w && d[x]+e[i].cost<d[v]) {
				d[v]=d[x]+e[i].cost;
				from[v]=i;
				if (!inq[v]) que[++qr]=v,inq[v]=true;
			}
			inq[x]=false;
		}
		if (d[t]==inf) return false;
		int mi=inf;
		for (int i=from[t];i;i=from[e[i].u]) mi=min(mi,e[i].w);
		flow+=mi,cost+=d[t]*mi;
		for (int i=from[t];i;i=from[e[i].u]) e[i].w-=mi,e[i^1].w+=mi;
		return true;
	}
	int mcmf(int S,int T) {
		int flow=0,cost=0;
		while (true) {
			if (!spfa(S,T,flow,cost)) break;
		}
		return cost;
	}
} G;
int main() {
#ifndef ONLINE_JUDGE
	freopen("test.in","r",stdin);
#endif
	n=read(),m=read();
	int S=0,T=n+2,ln=0,nn=0;
	for (int i=1;i<=n;++i) {
		nn=read();
		int x=ln-nn;
		if (x<0) G.add(i,T,-x); else G.add(S,i,x);
		ln=nn;
	}
	G.add(S,n+1,ln);
	for (int i=1;i<=n;++i) G.add(i,i+1,inf);
	for (int i=1;i<=m;++i) {
		int s=read(),t=read(),c=read();
		G.add(t+1,s,inf,c);
	}
	printf("%d\n",G.mcmf(S,T));
	return 0;
}
posted @ 2017-06-14 16:25  permui  阅读(378)  评论(0编辑  收藏  举报