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;
}

猜你喜欢

转载自www.cnblogs.com/OYJason/p/9862660.html