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
∑di∗ci
这不就是单纯形嘛
总之,我们得到了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)
∑j∣sj<=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
di−dj+dk=ai+1−ai+bi+1−bi
即
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
di−dj+dk−bi+1+bi=ai+1−ai
其中
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);
}