题面

传送门

思路

引入:网络流?

看到这道题,第一想法是用一个dp来完成决策

但是,显然这道题的数据并不允许我们进行dp,尤其是有10000种志愿者的情况下

那么我们就要想别的办法来解决:

贪心?这道题做的决策除了价值以外还有覆盖面问题,而且更致命的是每一天的人数需求还不一样

那么题目限制如此多元化的题目,我们就一定要想到用那个传说中的万能算法——网络流

想到网络流以后,一个直观的想法就是源点连人,人连可以被雇佣的日子,日子连汇点,然后跑最小费用最大流

但是这样有一个问题:本题中的雇佣不是可以一天用一天不用的,而是你花了那么多钱,他就一次性帮你从Si做到Ti,不能按天购买

而我们的这个网络流模型显然可以让流量从每个“人点”的一部分出边流出,并不正确

“一面对多面”

这里的这个问题,我称之为“一面对多面”,也就是一个决策会影响到多个限制条件的改变,但是网络流中的流量是一对一的,也就是一点流量不能在某一个点“分”成很多流量,所以并不能解决这种“一面对多面”的问题

那我们就要考虑更换思路了

我们之前的想法是以“一个被雇佣的人”为“一点流量”,但是仔细思索,发现出现上述问题的本质在于:不同的日子可能会用同一个人,而代表同一个人的流量只有一

那我们就要想办法让这一个流量,去覆盖所有的Si到Ti的日子

这引导我们想到如下模型:

对于每一中志愿者(si,ti,ci),我们建一条跨过si到ti的所有点的边,费用为ci,来表示“这一个流量一直流完了这些区域”

但是问题在于,只是这样建图的话,并没有把每天的人数限制ai放到图里,也就是这个图缺少信息

补全信息

此时我们需要想办法把人数限制放到图里

我们考虑最大流算法:它会求出最大的流量

那我们既然用一点流量表示一个人,那么为什么我们不把这个“需要用人”的限制,放到另外几条边上呢?

我们在点(i,i+1)之间建边,设流量为-a[i],也就是负的当天需求数,费用自然是零的

然后,令上文中的志愿者(si,ti,ci),建边(si,ti+1),费用ci,流量无限

此时我们相当于是把第i天的决策放到了第i个点和第i+1个点之间的所有边上(就是把所有点排成一排,这两个点之间的那一条位置里的所有边,包括跨过这个区间的志愿者边)

需要志愿者?让它们从志愿者边上流过去,同时让人数限制边满流到-a[i],这样求一个1-n+1的最大流,流量为0的最小费用就是雇佣人的最小费用了

为了让这个限制起效,又因为网络流中流量非负,所以我们建立点SS和TT,连边(SS,1)(n+1,TT),限制为inf,费用为0

同时,我们把之前的人数限制边的流量改成(inf-a[i]),这样最终的SS-TT最大流一定是inf,而且限制依然成立

完成

总结

到这里,我们就完成了本题的建模,可以看到这个过程是复杂而十分深刻的,我们从最开始的暴力、dp,转化到网络流,又从“一面对多面”的问题中跳了出来,转化到了最后的方法,又增加了inf的流量来使整个图合法

可以看到,这道题里主要解决的就是两个矛盾:dp时间复杂度不够,以及“一面对多面”不可行

第一个矛盾是时间上的,我们发现无法进行优化以后,转换了算法

第二个矛盾在于建模之上,我们采取了研究算法本质、重新构建模型的方法,舍弃了常见的建图模型而建立了这道题的独有模型

由此可见,做题的时候思路一定要放开,要大胆;如果发现常用的“套路”不合适,就要果断放弃、选择新方法

同时,研究算法、建立模型要从算法本质出发;一定要先想好算法中的什么东西代表题目中的哪个东西,再放到算法框架中,针对性构建模型;不能生搬硬套旧套路

Code

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define inf 1e9
using namespace std;
inline int read(){
int re=0,flag=1;char ch=getchar();
while(ch>'9'||ch<'0'){
if(ch=='-') flag=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9') re=(re<<1)+(re<<3)+ch-'0',ch=getchar();
return re*flag;
}
int n,m,cnt=-1,ans,first[10010],dis[10010],vis[10010];
struct edge{
int to,next,w,cap;
}a[200010];
inline void add(int u,int v,int w,int cap){
a[++cnt]=(edge){v,first[u],w,cap};first[u]=cnt;
a[++cnt]=(edge){u,first[v],-w,0};first[v]=cnt;
}
int q[100010];
bool spfa(int s,int t){
int head=0,tail=1,i,u,v,w;
memset(dis,-1,sizeof(dis));memset(vis,0,sizeof(vis));
q[0]=t;vis[t]=1;dis[t]=0;
while(head<tail){
u=q[head++];vis[u]=0;
for(i=first[u];~i;i=a[i].next){
v=a[i].to;w=a[i].w;
if(a[i^1].cap&&((dis[v]==-1)||(dis[v]>dis[u]-w))){
dis[v]=dis[u]-w;
if(!vis[v]) q[tail++]=v,vis[v]=1;
}
}
}
return ~dis[s];
}
int dfs(int u,int t,int limit){
if(u==t||!limit){vis[u]=1;return limit;}
int i,v,f,flow=0,w;vis[u]=1;
for(i=first[u];~i;i=a[i].next){
v=a[i].to;w=a[i].w;
if(!vis[v]&&(dis[v]==dis[u]-w)&&(a[i].cap)){
if(!(f=dfs(v,t,min(limit,a[i].cap)))) continue;
a[i].cap-=f;a[i^1].cap+=f;ans+=f*w;
flow+=f;limit-=f;
if(!limit) return flow;
}
}
return flow;
}
int zkw(int s,int t){//zkw费用流
int re=0;
while(spfa(s,t)){
vis[t]=1;
while(vis[t]){
memset(vis,0,sizeof(vis));
re+=dfs(s,t,inf);
}
}
return re;
}
int main(){
int i,t1,t2,t3;memset(first,-1,sizeof(first));
n=read();m=read();
for(i=1;i<=n;i++) t1=read(),add(i,i+1,0,inf-t1);//构建“人数限制边”
add(0,1,0,inf);add(n+1,n+2,0,inf);
for(i=1;i<=m;i++){//构建“志愿者边”
t1=read();t2=read();t3=read();
add(t1,t2+1,t3,inf);
}
zkw(0,n+2);//最大流值一定是inf,费用就是答案
cout<<ans<<endl;
}

[NOI2008][bzoj1061] 志愿者招募 [费用流+巧妙的建图]的更多相关文章

  1. 从多种角度看[BZOJ 1061] [NOI 2008]志愿者招募(费用流)

    从多种角度看[BZOJ 1061] [NOI 2008]志愿者招募(费用流) 题面 申奥成功后,布布经过不懈努力,终于成为奥组委下属公司人力资源部门的主管.布布刚上任就遇到了一个难题:为即将启动的奥运 ...

  2. P3980 [NOI2008]志愿者招募 费用流 (人有多大胆地有多大产

    https://www.luogu.org/problemnew/show/P3980 感觉费用流比网络流的图更难想到,要更大胆.首先由于日期是连续的,所以图中的点是横向排列的. 这道题有点绕道走的意 ...

  3. [BZOJ1061] [Noi2008] 志愿者招募 (费用流)

    Description 申奥成功后,布布经过不懈努力,终于成为奥组委下属公司人力资源部门的主管.布布刚上任就遇到了一个难 题:为即将启动的奥运新项目招募一批短期志愿者.经过估算,这个项目需要N 天才能 ...

  4. BZOJ 1061: [Noi2008]志愿者招募 费用流

    1061: [Noi2008]志愿者招募 题目连接: http://www.lydsy.com/JudgeOnline/problem.php?id=1061 Description 申奥成功后,布布 ...

  5. 志愿者招募 HYSBZ - 1061(公式建图费用流)

    转自神犇:https://www.cnblogs.com/jianglangcaijin/p/3799759.html 题意:申奥成功后,布布经过不懈努力,终于 成为奥组委下属公司人力资源部门的主管. ...

  6. [NOI2008] [bzoj1061] 志愿者招募

    还是一道费用流的题目.话不多说,进入正题. 题意:给定n个点和m种从l到r覆盖一层的费用,求满足所有点的覆盖层数都大等于权值的最小费用 分析:要做到区间修改,看似比较麻烦. 用差分把区间修改变成单点修 ...

  7. [NOI2008]志愿者招募 (费用流)

    大意: $n$天, 第$i$天要$a_i$个志愿者. $m$种志愿者, 每种无限多, 第$i$种工作时间$[s_i,t_i]$花费$c_i$, 求最少花费. 源点$S$连第一天, 容量$INF$ 第$ ...

  8. P3980 [NOI2008]志愿者招募 (费用流)

    题意:最多1000天 每天需要至少ai个工人施工 有10000种工人可以雇佣 每种工人可以工作si到ti天 雇佣一个的花费是ci 问怎样安排使得施工花费最少 思考:最直白的建模方式 就是每种工人可以和 ...

  9. Vijos1825 NOI2008 志愿者招募 费用流

    Orz ByVoid大神的题解:https://www.byvoid.com/blog/noi-2008-employee/ 学习网络流建图的好题,不难想到线性规划的模型,不过利用模型的特殊性,结合网 ...

随机推荐

  1. Eureka 微服务注册中心搭建

    本机IP为  192.168.1.102 1.   新建Maven项目   eureka 2.   pom.xml <project xmlns="http://maven.apach ...

  2. linux的一些指令

    linux的一些指令 █查看指令參數man 指令,如:man ls ,按q鍵退出 █查看文件列表ls -lht 按時間排序ll 列表ls 列表 目錄cd 目錄名稱 進入路徑cd .. 返回上層路徑 █ ...

  3. innobackup 参数

    innobackupex [--compress] [--compress-threads=NUMBER-OF-THREADS] [--compress-chunk-size=CHUNK-SIZE] ...

  4. 嵌入式开发 centos7 交叉编译环境准备

    1. 安装centos7,启动图像化界面. 参考:https://blog.csdn.net/qq_23014435/article/details/74347925 # systemctl get- ...

  5. 【mysql】 数据库字符集和排序规则

    库的字符集影响表和字段的字符集 数据库字符集 >表的字符集 > 字段的字符集 (从前往后优先级由低到高,从左往右继承,如果表没设置字符集,继承数据库的,如果字段没设置,继承表的) 数据库的 ...

  6. ARM Linux内核源码剖析索引

    start_kernel -->asm-offset.h 生成 -->proc_info_list   -->machine_desc -->__vet_atags --> ...

  7. 笔记-数据库-redis

    笔记-数据库-redis 1.      redis简介 Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库.缓存和消息中间件. 它支持多种类型的数据结构,如 stri ...

  8. this.$router 和this.$route 的区别

    1. this.$router: 表示全局路由器对象,项目中通过router路由参数注入路由之后,在任何一个页面都可以通过此方法获取到路由器对象,并调用其push(), go()等方法: 2. thi ...

  9. Error:Execution failed for task ':myapplication:processDebugResources'. > com.android.ide.common.pro

    Error:Execution failed for task ':myapplication:processDebugResources'. > com.android.ide.common. ...

  10. oracle 迭代查询

    Oracle 迭代查询, 以后台菜单作为示例 这是要准备的sql create table tbl_menu( id number primary key, parent_id , name ) no ...