[NOI2008]志愿者招募 [Zjoi2013]防守战线 一种网络流套路

43 篇文章 0 订阅
10 篇文章 0 订阅

Description

志愿者招募

申奥成功后,布布经过不懈努力,终于成为奥组委下属公司人力资源部门的主管。布布刚上任就遇到了一个难题:为即将启动的奥运新项目招募一批短期志愿者。经过估算,这个项目需要N 天才能完成,其中第i 天至少需要Ai 个人。布布通过了解得知,一共有M 类志愿者可以招募。其中第i 类可以从第Si 天工作到第Ti 天,招募费用是每人Ci 元。新官上任三把火,为了出色地完成自己的工作,布布希望用尽量少的费用招募足够的志愿者,但这并不是他的特长!于是布布找到了你,希望你帮他设计一种最优的招募方案。

防守战线

战线可以看作一个长度为n 的序列,现在需要在这个序列上建塔来防守敌兵,在序列第i 号位置上建一座塔有Ci 的花费,且一个位置可以建任意多的塔,费用累加计算。有m 个区间[L1, R1], [L2, R2], …, [Lm, Rm],在第i 个区间的范围内要建至少Di 座塔。求最少花费。

Analyze

这两题乍一看很像的样子,先分析志愿者招募这题:

给个样例吧,比较好写式子

3 3
2 3 4
1 2 2
2 3 5
3 3 2

令每类人的招聘次数为 d i d_i di,则
d 1 + d 2 > a 1 = 2 d_1+d_2>a_1=2 d1+d2>a1=2
d 1 + d 2 > a 2 = 3 d_1+d_2>a_2=3 d1+d2>a2=3
d 2 + d 3 > a 3 = 4 d_2+d_3>a_3=4 d2+d3>a3=4
最小化 ∑ d i ∗ c i \sum di*ci dici
这不就是单纯形嘛

总之,我们得到了n个形如下面式(i)的式子
∑ s j < = i < = t j d j > = a i \sum _{s_j<=i<=t_j} d_j>=a_i sj<=i<=tjdj>=ai (i)
变成等式
∑ j ∣ s j < = i < = t j d j = a i + b i ( b i > 0 ) \sum _{j|s_j<=i<=t_j} d_j=a_i+b_i (b_i>0) jsj<=i<=tjdj=ai+bi(bi>0) (i)
构造n-1个不等式,第i个不等式为原来的(i+1)式减(i)式。

由于题目的特性,可以发现对于每个di,有它出现的不等式一定是连续的一段
因此,在上下两式相减之后每个di就只会出现两次,而且一次为正,一次为负
形如:
d i − d j + d k = a i + 1 − a i + b i + 1 − b i d_i-d_j+d_k=a_{i+1}-a_i+b_{i+1}-b_i didj+dk=ai+1ai+bi+1bi

d i − d j + d k − b i + 1 + b i = a i + 1 − a i d_i-d_j+d_k-b_{i+1}+b_i=a_{i+1}-a_i didj+dkbi+1+bi=ai+1ai
其中 d i , d j , d k d_i,d_j,d_k di,dj,dk在所有式子中只会出现两次,一次为正,一次为负。
这启事我们用这个等式构造等量关系,也就是网络流的流量平衡。
每个点代表一个等式,令有源点S和汇点T。
对于每个等式i:
若右边为正数,则S向i连流量为右边的值,费用为0的边;
若右边为负数,则S向i连流量为右边的值的绝对值,费用为0的边;
对于每个d和b,从正的那个等式向负的那个等式连流量为正无穷,费用为费用的边。
这里的d和b的边的流量就是它们的取值,保证流量平衡就是保证等式成立。

跑最小费用最大流即可。

以下图片盗自 http://cxjyxx.me/?p=261

而对于防守战线这一题,情况就不太一样了(当然你要是会线性规划还是直接上模板)
我们将不等式用一个类似矩阵的形式表示出来:

对于志愿者招募,系数矩阵类似这样,每个di的出现是竖着连续的,所以上下相减后可以变得只剩一正一负。
在这里插入图片描述
然而对于防守战线,系数矩阵就类似下图:
在这里插入图片描述
它的每个di出现的矩阵是横着的,差分之后什么都不是。

于是可以考虑对偶问题(这TM是怎么于是出来的)
在这里插入图片描述
这张图太神了,看看可以理解吧?
转一下那位大佬的解释:

对于原问题 可以描述为:
有一个工厂 它生产n种产品 第i种产品可以卖ci元
现在一共有m种材料 生产一个产品i 需要aij个材料j
每个材料的个数有上限 为bi
现在要求一种生产方案使得获利最多

这个问题的对偶问题 可以描述为:
你现在要找这个工厂购买原材料 第i种材料需要bi个 价格由你定
这个工厂会把材料卖给你 仅当它觉得不亏
即它把卖给你的材料拿去做产品的价值<=你收购做这个产品所需材料的价格和
求最少需要多少¥可以收购完

这两个问题为对偶问题 他们的答案算出来是一样的
问题转移的方法如那个表所示
这样可以使系数矩阵顺时针旋转90度~
就可以把这题的不可做的系数矩阵转为可以做的矩阵了

是不是很有道理?
于是这题的对偶问题就是有若干种志愿者,每个出现的区间是[li,ri],费用是di,第j天至少要cj个志愿者。
变回志愿者招募了??!!
这样就可以用同样的方法建图解决。

Code

在使用ZKW费用流具体实现的时候会发现一些问题。
对于防守战线,你需要求的是最大费用最大流,将费用取反之后,可能通过费用为0的边就达到了最大流,但这是不是费用最小的,而程序就停止了。
解决办法是在第一次增广之前,先通过一次SPFA求出最短路求出初始的dis,就能避免出现上面的情况。

防守战线的程序

#include<cstdio>
#include<cstring>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define N 201000
#define inf 0x3fffffff
using namespace std;
int a[N],n,m,s[N],t[N],c[N],las[N],nxt[N*10],to[N*10],dat[N*10],fee[N*10],tot=1,S,T,ans=0,dis[N],bz[N],f[N],que[N];
void putin(int x,int y,int z1,int z2)
{
	nxt[++tot]=las[x];las[x]=tot;to[tot]=y;dat[tot]=z1,fee[tot]=z2;
	nxt[++tot]=las[y];las[y]=tot;to[tot]=x;dat[tot]=0,fee[tot]=-z2;
}
int dg(int x,int t)
{
	if(x==T) return t;
	int jy=0;bz[x]=1;
	for(int i=las[x];i;i=nxt[i])
	{
		int y=to[i];
		if(!bz[y]&&dat[i]&&dis[x]==dis[y]+fee[i])
		{
			int q=dg(y,min(t,dat[i]));
			dat[i]-=q;dat[i^1]+=q;t-=q;jy+=q;
			ans=ans+q*fee[i];
			if(t==0) return jy;
		}
	}
	return jy;
}
bool change()
{
	int minh=inf;
	fo(x,1,T) if(bz[x])
	for(int i=las[x];i;i=nxt[i])
	{
		int y=to[i];
		if(!bz[y]&&dat[i]>0) minh=min(minh,dis[y]-dis[x]+fee[i]);
	}
	if(minh==0||minh==inf) return 0;
	fo(x,1,T) if(bz[x]) dis[x]+=minh;
	return 1;
}
void spfa()
{
	memset(f,60,sizeof(f));
	que[1]=S;f[S]=0;bz[S]=1;
	int he=0,ta=1;
	while(he<ta)
	{
		int x=que[++he];
		for(int i=las[x];i;i=nxt[i])
		if(dat[i]>0)
		{
			int y=to[i];
			if(f[x]+fee[i]<f[y])
			{
				f[y]=f[x]+fee[i];
				dis[y]=dis[x]-fee[i];
				if(!bz[y]) que[++ta]=y;
			}
		}
		bz[x]=0;
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	S=n+2,T=S+1;
	fo(i,1,n+1)
	{
		if(i<=n) scanf("%d",&a[i]);
		if(a[i]>=a[i-1])
		{
			putin(i,T,a[i]-a[i-1],0);
		}
		else
		{
			putin(S,i,a[i-1]-a[i],0);
		}
	}
	fo(i,1,n) putin(i+1,i,inf,0);
	fo(i,1,m)
	{
		scanf("%d%d%d",&s[i],&t[i],&c[i]);
		putin(t[i]+1,s[i],inf,-c[i]);
	}
	spfa();
	do do
		memset(bz,0,sizeof(bz));
		while(dg(S,inf));
	while(change());
	printf("%d\n",-ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值