bzoj1061: [Noi2008]志愿者招募

46 篇文章 0 订阅
21 篇文章 0 订阅

传送门
思路:

例如一共需要4天,四天需要的人数依次是4,2,5,3。有5类志愿者,如下表所示:


设雇佣第i类志愿者的人数为X[i],每个志愿者的费用为V[i],第j天雇佣的人数为P[j],则每天的雇佣人数应满足一个不等式,如上表所述,可以列出

P[1]=X[1]+X[2]>=4

P[2]=X[1]+X[3]>=2

P[3]=X[3]+X[4]+X[5]>=5

P[4]=X[5]>=3

对于第i个不等式,添加辅助变量Y=0”>i,可以使其变为等式

P[1]=X[1]+X[2]-Y[1]=4

P[2]=X[1]+X[3]-Y[2]=2

P[3]=X[3]+X[4]+X[5]-Y[3]=5

P[4]=X[5]-Y[4]=3

在上述四个等式上下添加P[0]=0,P[5]=0,每次用下边的式子减去上边的式子,得出

① P[1]-P[0]=X[1]+X[2]-Y[1]=4

② P[2]-P[1]=X[3]-X[2]-Y[2]+Y[1]=-2

③ P[3]-P[2]=X[4]+X[5]-X[1]-Y[3]+Y[2]=3

④ P[4]-P[3]=-X[3]-X[4]+Y[3]-Y[4]=-2

⑤ P[5]-P[4]=-X[5]+Y[4]=-3

观察发现,每个变量都在两个式子中出现了,而且一次为正,一次为负.所有等式右边和为0.我们将最后的五个等式进一步变形,得出以下结果

① -X[1]-X[2]+Y[1]+4=0

② -X[3]+X[2]+Y[2]-Y[1]-2=0

③ -X[4]-X[5]+X[1]+Y[3]-Y[2]+3=0

④ X[3]+X[4]-Y[3]+Y[4]-2=0

⑤ X[5]-Y[4]-3=0

可 以发现,每个等式左边都是几个变量和一个常数相加减,右边都为0,恰好就像网络流中除了源点和汇点的顶点都满足流量平衡。每个正的变量相当于流入该顶点的 流量,负的变量相当于流出该顶点的流量,而正常数可以看作来自附加源点的流量,负的常数是流向附加汇点的流量。因此可以据此构造网络,求出从附加源到附加 汇的网络最大流,即可满足所有等式。而我们还要求费用最小,所以要在X变量相对应的边上加上权值,然后求最小费用最大流。

接下来,根据上面五个等式构图。

(1)每个等式为图中一个顶点,添加源点S和汇点T。

(2)如果一个等式中的数字为非负整数c,从源点S向该等式对应的顶点连接一条容量为c,权值为0的有向边;如果为负整数-c,从该等式对应的顶点向汇点T连接一条容量为c,权值为0的有向边。

(3)如果一个变量X[i]在第j个等式中出现为-X[i],在第k个等式中出现为+X[i],从顶点j向顶点k连接一条容量为INF,权值为V[i]的有向边。

(4)如果一个变量Y[i]在第j个等式中出现为-Y[i],在第k个等式中出现为+Y[i],从顶点j向顶点k连接一条容量为INF,权值为0的有向边。

构图以后,求从源点S到汇点T的最小费用最大流,费用值就是结果。
当然,我不会单纯形,orzPoPoQQQ

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<cstdlib>
#define inf 0x3f3f3f3f
#define N 1005
using namespace std;
struct edge{int from,to,next,f,c;}e[N*50];
int head[N],q[N],v[N],from[N],dis[N],d[N];
int n,m,S,T,tot=1,ans;
void add(int x,int y,int f,int c){
    e[++tot]=(edge){x,y,head[x],f,c};
    head[x]=tot;
    e[++tot]=(edge){y,x,head[y],0,-c};
    head[y]=tot;
}
bool spfa(){
    for (int i=1;i<=T;i++) dis[i]=inf;
    int h=dis[S]=0,t=v[S]=1,x; q[1]=S;
    while (h!=t){
        h=(h+1)%N;
        x=q[h];
        v[x]=0;
        for (int i=head[x];i;i=e[i].next)
            if (dis[e[i].to]>dis[x]+e[i].c&&e[i].f){
                dis[e[i].to]=dis[x]+e[i].c;
                from[e[i].to]=i;
                if (!v[e[i].to]){
                    v[e[i].to]=1;
                    t=(t+1)%N;
                    q[t]=e[i].to;
                }
            }
    }
    return dis[T]!=inf;
}
void del(){
    int x=inf;
    for (int i=from[T];i;i=from[e[i].from]) x=min(x,e[i].f);
    for (int i=from[T];i;i=from[e[i].from]){
        e[i].f-=x; e[i^1].f+=x; ans+=e[i].c*x; 
    }
}
int main(){
    scanf("%d%d",&n,&m);
    S=n+2; T=n+3;
    for (int i=1;i<=n;i++) scanf("%d",&d[i]);
    for (int i=1,x,y,v;i<=m;i++){
        scanf("%d%d%d",&x,&y,&v);
        add(x,y+1,inf,v);
    }
    for (int i=1,tmp;i<=n+1;i++){
        tmp=d[i]-d[i-1];
        if (tmp>=0) add(S,i,tmp,0);
        else add(i,T,-tmp,0);
        if (i>1) add(i,i-1,inf,0);
    }
    while (spfa()) del();
    printf("%d",ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值