BZOJ1061 NOI2008 志愿者招募
题目大意
给定$n$天,每天有一个雇佣志愿者数量的下限$D_i$,有$m$中志愿者,第$i$种可以在第$S_i$天到第$T_i$天工作,雇佣一个需要花费$C_i$元,每种可以雇佣无限个,求满足需求的最小代价。
题解
费用流好题
考虑先对这$n$个需求建立不等式,那么设第$k$种志愿者雇佣了$X_k$个,$G_{k,i}=1$表示第$k$个志愿者者可以在第$i$天工作,$=0$表示不可以。$$D_i\leq\sum\limits_{i=1}^m X_i\times G_{k,i}$$试图让一个不等式转化为符合网络流入度出度相等的等式。设置一个任意非负整数变量为$Y$,那么$$D_i+Y_i=\sum\limits_{i=1}^m X_i\times G_{k,i}$$
$$-D_i-Y_i+\sum\limits_{i=1}^m X_i\times G_{k,i}=0$$
现在的等式显然不符合建图要求,而我们的目的是将每一个变量视为一条边,那么要求每个变量只能出现恰好两次,且符号相反(为了保证一个变量只能出现一种权值且能符合反向边的意义)。
构造一个很巧妙的方法,那就是将两两相邻的不等式相减,类似差分一样,就得到了
$$-(D_i-D_{i-1})-(Y_i-Y_{i-1})+\sum\limits_{i=1}^m X_i\times (G_{k,i}-G_{k,i-1})=0$$
特别的我们定义$i=0,i=n+1$的所有项均为$0$。
显而易见,当$i=S_k$时$G_{k,i}-G_{k,i-1}=1$,当$i=T_k$时$G_{k,i}-G_{k,i-1}=-1$,其余情况均为$0$。
同样的,其余的变量也会恰好出现正负两次。
对于$n+1$个等式,看做一个点,处理出$-(D_i-D_{i-1})$,由于它是常数,所以它应该向源点和汇点连边。
具体的,若它$>0$,则从源点向该点连容量为该常数的边,否则从它向汇点连容量为该常数绝对值大小的边,费用均为$0$。
不难发现$Y$是任意,且$Y_i$在第$i$个等式中是正的,在第$i-1$个等式中是负的,那么要从第$i$个点向第$i-1$个点连容量为正无穷,费用为$0$的边。
而$X_k$在第$S_k$个不等式中是正的,在第$T_k+1$个不等式中是负的,那么从第$S_k$个不等式向第$T_k+1$个点连容量为正无穷,费用为$C_k$的边。
跑最小费用最大流,这样一来所有变量都会为了满足最大流的性质而符合要求。
#include<algorithm> #include<iostream> #include<cstring> #include<cstdio> #include<cmath> #define LL long long #define M 100020 #define N 2020 #define INF 1000000000 using namespace std; namespace IO{ const int BS=(1<<20)+5; char Buffer[BS],*HD,*TL; char Getchar(){if(HD==TL){TL=(HD=Buffer)+fread(Buffer,1,BS,stdin);} return (HD==TL)?EOF:*HD++;} int read(){ int nm=0,fh=1; char cw=Getchar(); for(;!isdigit(cw);cw=Getchar()) if(cw=='-') fh=-fh; for(;isdigit(cw);cw=Getchar()) nm=nm*10+(cw-'0'); return nm*fh; } }using namespace IO; int n,m,S,T,p[N],v[M],fs[N],nt[M<<1],to[M<<1],r[M<<1],c[M<<1],tmp; int dis[N],last[N],tar[N],ans,tot,q[M],hd,tl; bool vis[N]; inline void addedge(int x,int y,int rm,int ct){ nt[tmp]=fs[x],fs[x]=tmp,to[tmp]=y,r[tmp]=rm,c[tmp++]=ct; nt[tmp]=fs[y],fs[y]=tmp,to[tmp]=x,r[tmp]=0,c[tmp++]=-ct; } bool SPFA(){ memset(dis,0x3f,sizeof(int)*(T+2)); memset(vis,false,sizeof(bool)*(T+2)),dis[q[(tl=1)-1]=S]=hd=0; while(hd!=tl){ int x=q[hd++]; vis[x]=false; if(hd==M) hd=0; for(int i=fs[x];i!=-1;i=nt[i]){ if(!r[i]||dis[x]+c[i]>=dis[to[i]]) continue; dis[to[i]]=dis[x]+c[i],last[to[i]]=i,tar[to[i]]=min(tar[x],r[i]); if(!vis[to[i]]) vis[to[i]]=true,q[tl++]=to[i]; if(tl==M) tl=0; } } return dis[T]<INF; } void flow(){ for(int now=T,tt=tar[T];now!=S;now=to[last[now]^1]) r[last[now]]-=tt,r[last[now]^1]+=tt; ans+=tar[T]*dis[T]; } int main(){ n=read(),m=read(),S=n+2,T=S+1,memset(fs,-1,sizeof(fs)),tar[S]=INF; for(int i=1;i<=n;i++) p[i]=read(),addedge(i+1,i,INF,0); while(m--){int u=read(),v=read(),cst=read();addedge(u,v+1,INF,cst);} for(int i=1;i<=n+1;i++){ if(p[i]>p[i-1]) addedge(S,i,p[i]-p[i-1],0); else addedge(i,T,p[i-1]-p[i],0); } while(SPFA()) flow(); printf("%d\n",ans); return 0; }