zoukankan      html  css  js  c++  java
  • 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_ileqsumlimits_{i=1}^m X_i imes G_{k,i}$$试图让一个不等式转化为符合网络流入度出度相等的等式。设置一个任意非负整数变量为$Y$,那么$$D_i+Y_i=sumlimits_{i=1}^m X_i imes G_{k,i}$$

    $$-D_i-Y_i+sumlimits_{i=1}^m X_i imes G_{k,i}=0$$

    现在的等式显然不符合建图要求,而我们的目的是将每一个变量视为一条边,那么要求每个变量只能出现恰好两次,且符号相反(为了保证一个变量只能出现一种权值且能符合反向边的意义)。

    构造一个很巧妙的方法,那就是将两两相邻的不等式相减,类似差分一样,就得到了

    $$-(D_i-D_{i-1})-(Y_i-Y_{i-1})+sumlimits_{i=1}^m X_i imes (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
    ",ans); return 0;
    }
  • 相关阅读:
    幂等性-接口安全性
    spring 事务
    Disruptor 并发框架
    java中锁的应用
    线程池原理
    并发队列阻塞式与非阻塞式的区别
    Swagger UI教程 API 文档神器 搭配Node使用
    linux ssh_config和sshd_config配置文件
    Linux中iptables设置详细
    Linux(Centos)之安装Redis及注意事项
  • 原文地址:https://www.cnblogs.com/OYJason/p/9862660.html
Copyright © 2011-2022 走看看