problem

luogu-P3980

solution

志愿者连续工作 [ s i , t i ] [s_i,t_i] [si​,ti​] 天,我们可以提炼出网络流二十四题中《最长k可重区间集问题》的模型。

同样地,把 1 ∼ n 1\sim n 1∼n 天抽象成一条 1 ∼ n + 1 1\sim n+1 1∼n+1 个点的链条。

  • 源点 s → 1 s\rightarrow 1 s→1 容量无穷费用零, n + 1 → t n+1\rightarrow t n+1→t 汇点 容量无穷费用零。
  • 然后 i → i + 1 i\rightarrow i+1 i→i+1 容量 ∞ − a i \infty-a_i ∞−ai​ 费用零。
  • 对于第 i i i 种志愿者, s i → t i + 1 s_i\rightarrow t_i+1 si​→ti​+1 容量无穷费用 c i c_i ci​。

最后跑最小费用最大流即为答案。

如果无法直接理解这样建图的正确性,可以考虑把网络图中的流量流起来。

如果从 s s s 沿着费用零的边向 t t t 流,由于链条上的边流量为 ∞ − a i \infty-a_i ∞−ai​,所以先前 ∞ \infty ∞ 的不能流完。

那么势必要通过有志愿者(花费)的边流。

假设流到了点 i i i,那么剩下不能流过 ( i , i + 1 ) (i,i+1) (i,i+1) 的流量我们得从 i i i 连出去的志愿者边流,并且流一个就要花费 c i c_i ci​ 的代价。

然后在点 x + 1 x+1 x+1 的时候这些流量又会汇合。

这就相当于招募了从 i i i 开始到 x x x 结束的志愿者。(当然可能有多个 x x x 结束点)

反正到最后 t t t 的时候,流量总和一定会汇聚成从 s s s 开始流的 ∞ \infty ∞。

你可以理解一队人去闯密室逃脱,在一定关卡要进行多人支线任务,需要大部队派一些人去完成,然后主线队继续往下走主线任务,到了一定关卡有些人完成了自己的支线任务可以归队了。最后通关的时候,一定是大家都从主线任务关卡口出来。

code

#include <bits/stdc++.h>
using namespace std;
#define maxn 2000
#define maxm 50000
#define int long long
#define inf 0x3f3f3f3f
struct node { int to, nxt, flow, cost; }E[maxm];
int head[maxn], dis[maxn], lst[maxn], vis[maxn], a[maxn];
int cnt = -1, n, m, s, t;
queue < int > q;void addedge( int u, int v, int w, int c ) {E[++ cnt] = { v, head[u], w, c }; head[u] = cnt;E[++ cnt] = { u, head[v], 0,-c }, head[v] = cnt;
}bool SPFA() {memset( lst, -1, sizeof( lst ) );memset( dis, 0x3f, sizeof( dis ) );q.push( dis[s] = 0 );while( ! q.empty() ) {int u = q.front(); q.pop(); vis[u] = 0;for( int i = head[u];~ i;i = E[i].nxt ) {int v = E[i].to;if( dis[v] > dis[u] + E[i].cost and E[i].flow ) {dis[v] = dis[u] + E[i].cost; lst[v] = i;if( ! vis[v] ) vis[v] = 1, q.push( v );}}}return ~ lst[t];
}int MCMF() {int ans = 0;while( SPFA() ) {int flow = inf;for( int i = lst[t];~ i;i = lst[E[i ^ 1].to] )flow = min( flow, E[i].flow );for( int i = lst[t];~ i;i = lst[E[i ^ 1].to] ) {E[i ^ 1].flow += flow;E[i].flow -= flow;ans += flow * E[i].cost;}}return ans;
}signed main() {memset( head, -1, sizeof( head ) );scanf( "%lld %lld", &n, &m );s = 0, t = n + 2;for( int i = 1;i <= n;i ++ ) scanf( "%lld", &a[i] );for( int i = 1;i <= n;i ++ ) addedge( i, i + 1, inf - a[i], 0 );addedge( s, 1, inf, 0 );addedge( n + 1, t, inf, 0 );for( int i = 1, u, v, w;i <= m;i ++ ) {scanf( "%lld %lld %lld", &u, &v, &w );addedge( u, v + 1, inf, w );}printf( "%lld\n", MCMF() );return 0;
}

solution(流量平衡)

假设共 3 3 3 天,第 i i i 天招募 p i p_i pi​ 人。

共有三类志愿者:

  • 从第 1 1 1 天工作到第 3 3 3 天,费用为 c 1 c_1 c1​,招募了 b 1 b_1 b1​ 人。
  • 从第 2 2 2 天工作到第 3 3 3 天,费用为 c 2 c_2 c2​,招募了 b 2 b_2 b2​ 人。
  • 从第 1 1 1 天工作到第 2 2 2 天,费用为 c 3 c_3 c3​,招募了 b 3 b_3 b3​ 人。

则有以下不等式:
{ b 1 + b 3 ≥ a 1 b 1 + b 2 + b 3 ≥ a 2 b 1 + b 2 ≥ a 3 \begin{cases} b_1+b_3\ge a_1\\b_1+b_2+b_3\ge a_2\\b_1+b_2\ge a_3 \end{cases} ⎩⎪⎨⎪⎧​b1​+b3​≥a1​b1​+b2​+b3​≥a2​b1​+b2​≥a3​​
记第 i i i 天招募的志愿者超出最少要求人数 d i d_i di​ 人,显然 d i ≥ 0 d_i\ge 0 di​≥0。则可改写成以下等式:
{ p 1 = b 1 + b 3 = a 1 + d 1 p 2 = b 1 + b 2 + b 3 = a 2 + d 2 p 3 = b 1 + b 2 = a 3 + d 3 \begin{cases}p_1=b_1+b_3=a_1+d_1\\p_2=b_1+b_2+b_3=a_2+d_2\\p_3=b_1+b_2=a_3+d_3\end{cases} ⎩⎪⎨⎪⎧​p1​=b1​+b3​=a1​+d1​p2​=b1​+b2​+b3​=a2​+d2​p3​=b1​+b2​=a3​+d3​​
将相邻两两等式作差后移项整理得:
{ p 1 = b 1 + b 3 = a 1 + d 1 p 2 − p 1 = b 2 − b 3 = a 2 − a 1 + d 2 − d 1 p 3 − p 2 = − b 3 = a 3 − a 2 + d 3 − d 2 − p 3 = − b 1 − b 2 = − a 3 − d 3 ⇒ { p 1 − p 0 = b 1 + b 3 − a 1 − d 1 = 0 p 2 − p 1 = b 2 − b 3 − a 2 + a 1 − d 2 + d 1 = 0 p 3 − p 2 = − b 3 − a 3 + a 2 − d 3 + d 2 = 0 p 4 − p 3 = − b 1 − b 2 + a 3 + d 3 = 0 \begin{cases}p_1=b_1+b_3=a_1+d_1\\p_2-p_1=b_2-b_3=a_2-a_1+d_2-d_1\\ p_3-p_2=-b_3=a_3-a_2+d_3-d_2\\-p_3=-b_1-b_2=-a_3-d_3\end{cases}\Rightarrow \begin{cases}p_1-p_0=b_1+b_3-a_1-d_1=0\\p_2-p_1=b_2-b_3-a_2+a_1-d_2+d_1=0\\ p_3-p_2=-b_3-a_3+a_2-d_3+d_2=0\\p_4-p_3=-b_1-b_2+a_3+d_3=0 \end{cases} ⎩⎪⎪⎪⎨⎪⎪⎪⎧​p1​=b1​+b3​=a1​+d1​p2​−p1​=b2​−b3​=a2​−a1​+d2​−d1​p3​−p2​=−b3​=a3​−a2​+d3​−d2​−p3​=−b1​−b2​=−a3​−d3​​⇒⎩⎪⎪⎪⎨⎪⎪⎪⎧​p1​−p0​=b1​+b3​−a1​−d1​=0p2​−p1​=b2​−b3​−a2​+a1​−d2​+d1​=0p3​−p2​=−b3​−a3​+a2​−d3​+d2​=0p4​−p3​=−b1​−b2​+a3​+d3​=0​
网络流中除了源汇点,其余点都应满足流量平衡,即流入流量等于流出流量;若将流入记为正,流出记为负,则应满足流入流出流量的代数和为 0 0 0。

网络图中一条连接 x , y x,y x,y 的边,在 x , y x,y x,y 的流量平衡等式中各出现一次,且一次为正一次为负。

所以我们可以对上面最后化出的等式每个建立一个点,这个等式表示的就是这个点流量平衡。

再观察最后的等式:

observationⅠ. \text{observationⅠ.} observationⅠ. b i , d i b_i,d_i bi​,di​ 都在恰好两个等式出现,且是一正一负。所以每一个变量 b i , d i b_i,d_i bi​,di​ 都可以作为网络图中的一条边。

observationⅡ. \text{observationⅡ.} observationⅡ. 常量 a i a_i ai​ 也恰好在两个等式中出现,且是一正一负。为正时表示流入,可以和源点连边;为负时表示流出,可以和汇点连边。

根据作差规则, a i a_i ai​ 一定是出现在第 i , i + 1 i,i+1 i,i+1 两个等式中,且一定第 i i i 个等式为负,第 i + 1 i+1 i+1 个为正。

常量与源汇点连边,变量表示常量点之间的边跑平衡。

最后答案是 min ⁡ ∑ b i ⋅ c i \min \sum b_i·c_i min∑bi​⋅ci​ ,可以以“费用”的形式表示出来。

  • 简述建图方式:

    假设 a 0 = a n + 1 = 0 a_0=a_{n+1}=0 a0​=an+1​=0。

    • 建立源汇点 s , t s,t s,t。
    • 建立点 1 ∼ n + 1 1\sim n+1 1∼n+1,代表 n + 1 n+1 n+1 个等式。
    • 第 i + 1 i+1 i+1 个点向第 i i i 个点连一条容量无穷,费用为零的边。对应 b i , d i b_i,d_i bi​,di​ 的平衡。
    • 第 i i i 类志愿者连边 s i → t i + 1 s_i\rightarrow t_i+1 si​→ti​+1,容量无穷,费用为 c i c_i ci​。
    • 对于第 i i i 个点,若 a i − a i − 1 a_i-a_{i-1} ai​−ai−1​ 为正,连边 s → i s\rightarrow i s→i,容量 a i − a i − 1 a_i-a_{i-1} ai​−ai−1​ 费用为零;若为负,连边 i → t i\rightarrow t i→t,容量 a i − 1 − a i a_{i-1}-a_i ai−1​−ai​ 费用为零。相当于等式中的常数项。

实际理解上可以把常数项提到右边:
{ b 1 + b 3 − d 1 = a 1 − a 0 b 2 − b 3 − d 2 + d 1 = a 2 − a 1 − b 3 − d 3 + d 2 = a 3 − a 2 − b 1 − b 2 + d 3 = a 4 − a 3 \begin{cases} b_1+b_3-d_1=a_1-a_0\\ b_2-b_3-d_2+d_1=a_2-a_1\\ -b_3-d_3+d_2=a_3-a_2\\ -b_1-b_2+d_3=a_4-a_3 \end{cases} ⎩⎪⎪⎪⎨⎪⎪⎪⎧​b1​+b3​−d1​=a1​−a0​b2​−b3​−d2​+d1​=a2​−a1​−b3​−d3​+d2​=a3​−a2​−b1​−b2​+d3​=a4​−a3​​
把 a i − a i − 1 a_i-a_{i-1} ai​−ai−1​ 当成第 i i i 个等式的盈亏量,这样你就能理解正负与源汇连边的意义了。

code

#include <bits/stdc++.h>
using namespace std;
#define maxn 2000
#define maxm 50000
#define int long long
#define inf 0x3f3f3f3f
struct node { int to, nxt, flow, cost; }E[maxm];
int head[maxn], dis[maxn], lst[maxn], vis[maxn], a[maxn];
int cnt = -1, n, m, s, t;
queue < int > q;void addedge( int u, int v, int w, int c ) {E[++ cnt] = { v, head[u], w, c }; head[u] = cnt;E[++ cnt] = { u, head[v], 0,-c }, head[v] = cnt;
}bool SPFA() {memset( lst, -1, sizeof( lst ) );memset( dis, 0x3f, sizeof( dis ) );q.push( dis[s] = 0 );while( ! q.empty() ) {int u = q.front(); q.pop(); vis[u] = 0;for( int i = head[u];~ i;i = E[i].nxt ) {int v = E[i].to;if( dis[v] > dis[u] + E[i].cost and E[i].flow ) {dis[v] = dis[u] + E[i].cost; lst[v] = i;if( ! vis[v] ) vis[v] = 1, q.push( v );}}}return ~ lst[t];
}int MCMF() {int ans = 0;while( SPFA() ) {int flow = inf;for( int i = lst[t];~ i;i = lst[E[i ^ 1].to] )flow = min( flow, E[i].flow );for( int i = lst[t];~ i;i = lst[E[i ^ 1].to] ) {E[i ^ 1].flow += flow;E[i].flow -= flow;ans += flow * E[i].cost;}}return ans;
}signed main() {memset( head, -1, sizeof( head ) );scanf( "%lld %lld", &n, &m );s = 0, t = n + 2;for( int i = 1;i <= n;i ++ ) scanf( "%lld", &a[i] );for( int i = 1;i <= n;i ++ ) addedge( i + 1, i, inf, 0 );for( int i = 1, u, v, w;i <= m;i ++ ) {scanf( "%lld %lld %lld", &u, &v, &w );addedge( u, v + 1, inf, w );}for( int i = 1;i <= n + 1;i ++ )if( a[i] - a[i - 1] > 0 ) addedge( s, i, a[i] - a[i - 1], 0 );else addedge( i, t, a[i - 1] - a[i], 0 );printf( "%lld\n", MCMF() );return 0;
}

其实流量平衡的建边含义理解还有从线性规划对偶角度出发的。会在《防守战线》中详细说明。

[NOI2008] 志愿者招募(线性规划-对偶问题-费用流)相关推荐

  1. BZOJ1061 NOI2008 志愿者招募 线性规划、费用流

    传送门 一道思路很妙的线性规划网络流 设\(X_i\)表示第\(i\)天需要的人数,\(P_i\)表示第\(i\)种人雇佣的个数 那么我们可以列出一系列式子 比如说样例就可以列出三个式子: \(P_1 ...

  2. bzoj 1061: [Noi2008]志愿者招募【最小费用最大流】

    神奇的建图:连接(s,1,inf,0)(n+1,t,inf,0),对于1~n连接(i,i+1,inf-a[i],0),对于每个志愿者(s,t,c),连接(s,t+1,inf,c). 因为从s开始的流是 ...

  3. BZOJ1061: [Noi2008]志愿者招募(线性规划)

    Time Limit: 20 Sec  Memory Limit: 162 MB Submit: 5725  Solved: 3437 [Submit][Status][Discuss] Descri ...

  4. 【费用流】BZOJ1061: [Noi2008]志愿者招募(这题超好)

    1061: [Noi2008]志愿者招募 Time Limit: 20 Sec  Memory Limit: 162 MB Submit: 5291  Solved: 3173 [Submit][St ...

  5. [BZOJ1061] [NOI2008] 志愿者招募 - 最小费用最大流

    大部分内容转自: BYVOID - NOI2008 志愿者招募  如果讲道理的话,就是说我们抽象一下这个模型--然后每条费用边就是连接起始日期和结束日期的边,也就是说这条边上的流量增加1,就要增加一个 ...

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

    [NOI2008] 志愿者招募 (费用流) 题目描述 申奥成功后,布布经过不懈努力,终于成为奥组委下属公司人力资源部门的主管.布布刚上任就遇到了一个难题:为即将启动的奥运新项目招募一批短期志愿者.经过 ...

  7. [BZOJ1061][Noi2008]志愿者招募

    [BZOJ1061][Noi2008]志愿者招募 试题描述 申奥成功后,布布经过不懈努力,终于成为奥组委下属公司人力资源部门的主管.布布刚上任就遇到了一个难 题:为即将启动的奥运新项目招募一批短期志愿 ...

  8. 单纯型法Ⅱ(bzoj 1061: [Noi2008]志愿者招募)

    线性规划单纯型法:http://blog.csdn.net/jaihk662/article/details/78050666 标准型:m个约束,n个变量,构成m*n的矩阵 C是一个n的向量,B是一个 ...

  9. 【BZOJ1061/3265】[Noi2008]志愿者招募/志愿者招募加强版 单纯形法

    [BZOJ1061][Noi2008]志愿者招募 Description 申奥成功后,布布经过不懈努力,终于成为奥组委下属公司人力资源部门的主管.布布刚上任就遇到了一个难题:为即将启动的奥运新项目招募 ...

最新文章

  1. POJ 3169 差分约束
  2. Oracle 备份与恢复学习笔记(6_1)
  3. 推荐系统里,可以用蒸馏吗?
  4. 数据结构学习之路-第一章:绪论
  5. linux mpeg-4,嵌入式MPEG-4解码系统的设计与实现,嵌入式MPEG-4解码系统,嵌入式Linux,视频码流,P...
  6. android opencv 银行卡识别,NDK 开发之使用 OpenCV 实现银行卡号识别
  7. python install zabbix.4.0
  8. TouchJSON的简单使用
  9. Java 构造器 通过私有构造器强化不可实例化的能力
  10. ezcad旋转轴标刻参数_EzCad 2.0 扩展轴标刻插件使用说明书简体中文(.pdf
  11. Arduino 超声波避障循迹小车,四轮智能小车
  12. PPP项目群管理模式的实践探索——以浙江交工富阳PPP项目群为例
  13. 以后你肯定会用到的,25个常用Matplotlib图的Python代码,可以不会不能没有,建议收藏
  14. c语言进程伪装,易语言程序伪装软件
  15. 按键精灵X学习笔记(二):键盘命令
  16. linux nand 驱动,Linux NAND FLASH驱动分析(一)
  17. css中实现三角形的几种方式
  18. 关于“要不要做调研”
  19. 华东地区响应最快的DNS服务排名
  20. 单片机中的浮点数转换成串口可打印格式

热门文章

  1. python和c语言哪个实用-c语言和python语言哪个更值得学?
  2. html中省略号的设置
  3. Python是个什么鬼?
  4. 中山大学计算机学院运动会,奔跑吧!中大人!直击中山大学2020年运动会精彩瞬间...
  5. 一文带你快速了解Java中的【this引用】
  6. 一流程序员的成长之路
  7. java幼儿园信息管理系统
  8. 1895. 安排面试城市
  9. 读灵遁者诗歌:像晚霞一样的谜团
  10. 法人的法定代表人怎样确定