bzoj1061志愿者招募 费用流or单纯形

       这道题目可以直接用单纯型对偶问题水过去,而且代码超短不到1k。。还是先讲一下费用流做法(但是并没有写):

       我推荐网上一种比较简单的方法(传送门),不过没有证明,我会在下面给出。

       首先从源点S给出一个很大的流F,连向表示第一天的点容量为F,然后对于第i天,连边i→i+1,容量为F-a[i],费用为0,特殊地,第n天连边n→T,容量为F-a[n],费用为0。我们定义这时第i个点连向第i+1个点的边为该点的出边。

       然后我们的目标是使到T的流量最终达到F,即每一天的人数的足够。不妨考虑一种志愿者i,如果雇佣xi个,则这xi个志愿者可以将第Si到Ti每一个点的出边的容量+xi,同时需要费用Ci*xi。实际上Si到Ti的每一个点的出边容量+xi就相当于从Si到Ti+1(Tn+1相当于汇点T)连一条容量为xi的边。但是xi我们是不知道的,是根据需要得到的,因此我们可以连边Si→Ti+1,容量为inf,费用为Ci。定义这样的边为新边。然后跑一遍最小费用最大流即可。显然最大流为F(可行的话),那么此时的最小费用流即最小的花费。

      简单证明一下正确性:显然这样的解是最优解;然后证明一下可行性:假设当最大流为F时,还有i没有满足人数约束,我们找到一个i,如果i没有满足人数约束,那么将所有跨越i的的新边拆成两条,一条先到i,一条再从i出发,那么显然答案不变。由于i不满足人数约束,那么显然从i出发的新边(包括拆好的)的容量之和<a[i],而i的出边容量为F-a[i],因此从i出发的容量之和<a[i]+F-a[i]=F,那么汇点T的容量显然不满足F,矛盾! 证毕

       然后就可以跑最小费用最大流了。。点O(N),边O(N+M)还是很快的。。期望复杂度O(FM)(woc~要爆炸)


      如果用线性规划做。。就很简单了,实际上是最小化一个东西,满足若干约束,而且形式是Σaixi>=b。这实际上就是一个标准型现行规划的对偶问题,转化一下就好了(具体见《算法导论》)

AC代码如下:

#include<iostream>
#include<cstdio>
#define eps 1e-6
using namespace std;

int n,m; double a[10005][1005];
void pivot(int x,int y){
	a[x][y]=1/a[x][y]; int i,j;
	for (i=1; i<=n+1; i++)
		if (i!=y) a[x][i]*=a[x][y];
	for (i=1; i<=m+1; i++) if (i!=x)
		for (j=1; j<=n+1; j++)
			if (j!=y) a[i][j]-=a[i][y]*a[x][j];
	for (i=1; i<=m+1; i++)
		if (i!=x) a[i][y]*=-a[x][y];
}
void simplex(){
	int i,j,k=0; a[m+1][0]=0;
	while (1){
		for (i=1; i<=n; i++) if (a[m+1][i]>a[m+1][k]) k=i;
		if (!k) return; j=1;
		for (i=2; i<=m; i++) if (a[i][k]>eps)
			if (a[i][n+1]*a[j][k]<a[j][n+1]*a[i][k]) j=i;
		pivot(j,k); k=0;
	}
}
int main(){
	scanf("%d%d",&n,&m); int i,j;
	for (i=1; i<=n; i++) scanf("%lf",&a[m+1][i]);
	for (i=1; i<=m; i++){
		int x,y; scanf("%d%d%lf",&x,&y,&a[i][n+1]);
		for (j=x; j<=y; j++) a[i][j]=1;
	}
	simplex(); printf("%.0f\n",-a[m+1][n+1]);
	return 0;
}


by lych

2016.2.20

你可能感兴趣的:(费用流,线性规划,单纯形)