bzoj 1061: [Noi2008]志愿者招募(线性规划+网络流)

Description

申奥成功后,布布经过不懈努力,终于成为奥组委下属公司人力资源部门的主管。布布刚上任就遇到了一个难题:为即将启动的奥运新项目招募一批短期志愿者。经过估算,这个项目需要N 天才能完成,其中第i 天至少需要Ai 个人。 布布通过了解得知,一共有M 类志愿者可以招募。其中第i 类可以从第Si 天工作到第Ti 天,招募费用是每人Ci 元。新官上任三把火,为了出色地完成自己的工作,布布希望用尽量少的费用招募足够的志愿者,但这并不是他的特长!于是布布找到了你,希望你帮他设计一种最优的招募方案。

Input

第一行包含两个整数N, M,表示完成项目的天数和可以招募的志愿者的种类。 接下来的一行中包含N 个非负整数,表示每天至少需要的志愿者人数。 接下来的M 行中每行包含三个整数Si, Ti, Ci,含义如上文所述。为了方便起见,我们可以认为每类志愿者的数量都是无限多的。

Output

仅包含一个整数,表示你所设计的最优方案的总费用。

本题可以说是最经典的一个线性规划与网络流结合的题了,对于这个题,,我当然是不会的,上网看了题解,,才发现有这么神的构图方法。下面说说我的一点理解:
首先我们先要明白网络流的一个性质:除了源点和汇点外,每个点的入流和出流是相等的。那么我们就可以根据这个性质来建图;首先我们先要将题目转化为一个数学模型:将其转化为一系列的等式或不等式,如果是不等式的话可以添加变量将其变为等式,这样我们就可以将每个等式看作一个点,那么等式的左右两端就分别是该点的入流和出流,,注意我们列的等式要满足几个要求:首先尽量将常量放在等式的一边,变量放在另一边;其次对于每个我们不知道的变量要做到该变量在所有等式中的权值和为0(即所有的等式带有变量的一边之和=0),所有等式的常量部分相加也应为0,这是为了保证整张图的入流和出流相等;
我讲的也不是特别好,建图部分和本题具体的详解可以参见另一篇大神的博文
https://www.byvoid.com/blog/noi-2008-employee/#more-916

下面是代码:

#include<iostream>
#include<queue>
#include<vector>
#include<cstring>
#include<cstdio>
#include<climits>
#define N 1020
#define INF INT_MAX/3
using namespace std;
struct Edge { 
    int fr,to,cap,cost;
    Edge(int f,int t,int ca,int co) { 
      fr=f;to=t;cap=ca;cost=co; 
    }
};
vector<int> g[N+10];vector<Edge> edge;
int n,m,ans,S=0,T=N,p[N+10],dis[N+10],ca[N+10],v[N+10];
void Add_edge(int a,int b,int cat,int cost) {
    edge.push_back(Edge(a,b,cat,cost));edge.push_back(Edge(b,a,0,-cost));
    int t=edge.size();g[a].push_back(t-2),g[b].push_back(t-1); return;
}
int spfa() {
    memset(dis,127/3,sizeof(dis));p[T]=-1;p[S]=-1;
    queue<int> q;q.push(S);ca[S]=INF;dis[S]=0;v[S]=true;
    while(!q.empty()) {
      int x=q.front(),l=g[x].size();q.pop();v[x]=false;
      for(int i=0;i<l;i++) {
        int ti=g[x][i],to=edge[ti].to;Edge &e=edge[ti];
        if(e.cap>0&&dis[to]>dis[x]+e.cost) {
          dis[to]=dis[x]+e.cost; p[to]=ti; ca[to]=min(e.cap,ca[x]);
          if(!v[to]) v[to]=true,q.push(to);
        }
      }
    }
    if(p[T]==-1) return 0;ans+=ca[T]*dis[T];
    for(int i=p[T];i!=-1;i=p[edge[i].fr]) {
      edge[i].cap-=ca[T];edge[i^1].cap+=ca[T];
    } return 1;
}
void min_cost() {
    while(spfa()); printf("%d",ans);return;
}
int in() {
    int s=0;char c=getchar(); while(c<'0'||c>'9') c=getchar();
    while(c>='0'&&c<='9') s=s*10+c-'0',c=getchar();return s;
}
int main() {
    int t=0,s=0;n=in(),m=in(); for(int i=1;i<=n;i++) {
      t=s;s=in();t=s-t;
      if(t==0) goto end;
      if(t>0) Add_edge(S,i,t,0); else Add_edge(i,T,t*(-1),0); 
      end: Add_edge(i+1,i,INF,0);
    } Add_edge(n+1,T,s,0);
    int c,l,r;for(int i=1;i<=m;i++) {
      l=in();r=in();c=in(); Add_edge(l,r+1,INF,c);
    } min_cost(); return 0;
}

PS: bzoj上面还有很多跑的很快的大神们,,据说是因为有特殊的建图方法。有兴趣的自己去找吧。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值