文化课 + 竞赛双废物又来水题解了。

首先,对于题干中的人,很像网络流中的流量,但是他有一个每天人数的下限,我从网上借鉴(chaoxi)到了两种思路:

  1. 把下界限制转化为一条边的流量下界,这样就是最小费用上下界最大流。
  2. 加入几个新值,其条件正好为 \(\ge 0\),将其当做一条流量,这样不等式就变成了等式,我们可以利用流量守恒,用点的流满 $\Leftrightarrow $ 等式成立,这样就变成了一个最小费用最大流,去掉了上下界的影响。

其实这两种思路本质好像是一致的...

思路一

即 \(y\) 总视频的解法,考虑到人的工作时间是一段区间,不妨差分考虑,先让 \(i\) 的人无条件免费顺延到 \(i + 1\) 的边,然后在 \(S\) 和 \(T\) 人为规定加入/去除即可,这样一类工作者只会设计常数的点边,复杂度正常。

那么建立网络 \(G\):

  • 对于一天 \(i\),让 \(i\) 点向 \(i + 1\) 点连边。流量下界是 \(A_i\)、上界是正无穷,费用是 \(0\),这条边的流量意味着第 \(i\) 天的工作人数,
  • 对于一类志愿者 \(j\),让 \(T_j + 1\) 向 \(S_j\) 连一条容量无界限,费用是 \(C_i\) 的边,这条边可以自由活动,意味着自由安排是志愿者。

这样,每个志愿者相当于在流网络的一单位的流量流过了一个环,这个问题变成了最小费用无源汇上下界可行流,我们都知道可行流的可行判定经过转化就是跑到最大流,所以转化完后用最小费用最大流就行了。

时间复杂度

\(O(n^2m)\) 复杂度真棒

Code

这题 Acwing 咋这么卡常啊

#include <iostream>
#include <cstdio>
#include <cstring>
#define rint register int
typedef long long LL; using namespace std; const int N = 1005, M = (N * 2 + 10000) * 2, INF = 0x3f3f3f3f; int n, m, a[N], incf[N], d[N], q[N], pre[N];
int head[N], numE = 1, S, T;
bool vis[N];
LL ans;
struct E{
int next, v, w, c;
} e[M]; void inline add(int u, int v, int w, int c) {
e[++numE] = (E) { head[u], v, w, c };
head[u] = numE;
e[++numE] = (E) { head[v], u, 0, -c };
head[v] = numE;
} bool inline spfa() {
memset(d, 0x3f, sizeof d);
rint hh = 0, tt = 1;
d[S] = 0, q[0] = S, incf[S] = INF;
while (hh != tt) {
rint u = q[hh++]; vis[u] = false;
if (hh == N) hh = 0;
for (rint i = head[u]; i; i = e[i].next) {
rint v = e[i].v;
if (d[u] + e[i].c < d[v] && e[i].w) {
d[v] = d[u] + e[i].c, pre[v] = i, incf[v] = min(incf[u], e[i].w);
if (!vis[v]) {
vis[v] = true, q[tt++] = v;
if (tt == N) tt = 0;
}
}
}
}
return d[T] != INF;
} void inline update() {
int x = T;
while (x != S) {
int i = pre[x];
e[i].w -= incf[T], e[i ^ 1].w += incf[T];
x = e[i ^ 1].v;
}
ans += (LL)d[T] * incf[T];
} int main() {
scanf("%d%d", &n, &m);
S = n + 2, T = n + 3;
for (rint i = 1, A; i <= n; i++) {
scanf("%d", &A);
add(i, i + 1, INF, 0);
a[i] -= A, a[i + 1] += A;
}
for (rint i = 1, s, t, c; i <= m; i++) {
scanf("%d%d%d", &s, &t, &c);
add(t + 1, s, INF, c);
}
for (rint i = 1; i <= n + 1; i++) {
if (a[i] > 0) add(S, i, a[i], 0);
else if (a[i] < 0) add(i, T, -a[i], 0);
}
while (spfa()) update();
printf("%lld\n", ans);
return 0;
}

思路二

设一些新的变量:

  • \(B_i (B_i \ge 0)\),表示第 \(i\) 天实际招了 \(A_i + B_i\) 人。

  • \(D_i (D_i \ge 0)\),表示实际上第 \(i\) 类志愿者招了 \(D_i\) 类人

这样我们就可以列出 \(n\) 个等式,对于第 \(i\) 个等式(针对第 \(i\) 天的匹配情况)

\[A_i + B_i = \displaystyle \sum_{S_j \le i \le T_j} D_j
\]

但是为了让每个变量在流网络中、在每个等式中都相等,所以我们得让每个变量至多出现在两个式子中,(如果出现在一个式子,就可以将其到源汇点的费用改了,这样就是费用对应上了,如果两个式子,可以从本该连向汇点的边直接连向本该从源点出的边,这样费用对应。这里本人实力还是非常菜,很可能讲了一些玄学的东西,求大佬们轻喷。)

由于每个 \(j\) 影响的 \(i\) 是连续的一段,所以我们可以将式子前后加入两个 \(0 = 0\),然后将式子差分(这是一步等价变换)这样每个 \(D_j, B_i\) 都恰好会出现在两个式子之中。

对于第 \(i\) 个等式而言,差分后的式子:

\[-A_{i-1} - B_{i-1} + A_i + B_i = \displaystyle -\sum_{i-1=T_j}D_j+\sum_{i=S_j}D_j
\]

我们把式子移项,让每一项都是正的:

\[A_i + B_i + \displaystyle\sum_{i-1=T_j} D_j = A_{i-1}+B_{i-1}+\sum_{i=S_j}D_j
\]

这样,我们可以把等式看作一个点的流量守恒等式,等式左右两侧分别是流入该点/流出改点的流量,我们建立流网络:

  • 对于常量 \(A\),在左侧则连一条自虚拟源点出发,到 \(i\) 点,流量为 \(A_i\),无费用的边,右侧连到汇点,即对称的。
  • 对于变量 \(B\),从 \(B_i\) 所在右侧等式的点向 \(B_i\) 所在左侧的点,即 \(i\) 连向 \(i-1\),这条边流量无限,意味着自由选择的 \(B\),而这条边 + 流量守恒保证了 \(B_i\) 在两个式子中不变
  • 对于变量 \(D\) 同理,即 \(S_j\) 连向 \(T_j + 1\),流量无限,费用为 \(C_j\) 的边。

这样,在流网络跑到的最大流 = 从 \(S\) 出发所有容量(满足常量的强行限制) \(\Leftrightarrow\) 差分等式成立 \(\Leftrightarrow\) 原始等式成立 \(\Leftrightarrow\) 一个满足条件的方案

所以,原问题最小费用 \(\Leftrightarrow\) 最小费用最大流

时间复杂度

\(O(n^2m)\)

Code

这个代码过不去acwing。。坑

#pragma GCC optimize("Ofast","-funroll-loops")
#include <iostream>
#include <cstdio>
#include <cstring>
#define rint register int
typedef long long LL; using namespace std; const int N = 1005, M = (N * 3 + 10000) * 2, INF = 0x3f3f3f3f; int n, m, a[N], incf[N], d[N], q[N], pre[N];
int head[N], numE = 1, S, T;
bool vis[N];
LL ans;
struct E{
int next, v, w, c;
} e[M]; void inline add(int u, int v, int w, int c) {
e[++numE] = (E) { head[u], v, w, c };
head[u] = numE;
e[++numE] = (E) { head[v], u, 0, -c };
head[v] = numE;
} bool inline spfa() {
memset(d, 0x3f, sizeof d);
rint hh = 0, tt = 1;
d[S] = 0, q[0] = S, incf[S] = INF;
while (hh != tt) {
rint u = q[hh++]; vis[u] = false;
if (hh == N) hh = 0;
for (rint i = head[u]; i; i = e[i].next) {
rint v = e[i].v;
if (d[u] + e[i].c < d[v] && e[i].w) {
d[v] = d[u] + e[i].c, pre[v] = i, incf[v] = min(incf[u], e[i].w);
if (!vis[v]) {
vis[v] = true;
q[tt++] = v;
if (tt == N) tt = 0;
}
}
}
}
return d[T] != INF;
} void inline update() {
int x = T;
while (x != S) {
int i = pre[x];
e[i].w -= incf[T], e[i ^ 1].w += incf[T];
x = e[i ^ 1].v;
}
ans += (LL)d[T] * incf[T];
} int main() {
scanf("%d%d", &n, &m);
S = n + 2, T = n + 3;
for (rint i = 1, A; i <= n; i++) {
scanf("%d", &A);
add(S, i, A, 0), add(i + 1, T, A, 0);
add(i + 1, i, INF, 0);
}
for (rint i = 1, s, t, c; i <= m; i++) {
scanf("%d%d%d", &s, &t, &c);
add(s, t + 1, INF, c);
}
while (spfa()) update();
printf("%lld\n", ans);
return 0;
}

NOI2008 志愿者招聘的更多相关文章

  1. BZOJ 1061: [Noi2008]志愿者招募

    1061: [Noi2008]志愿者招募 Time Limit: 20 Sec  Memory Limit: 162 MBSubmit: 4064  Solved: 2476[Submit][Stat ...

  2. BZOJ 1061: [Noi2008]志愿者招募 [单纯形法]【学习笔记】

    1061: [Noi2008]志愿者招募 Time Limit: 20 Sec  Memory Limit: 162 MBSubmit: 3975  Solved: 2421[Submit][Stat ...

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

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

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

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

  5. bzoj1061: [Noi2008]志愿者招募

    线性规划与费用流.http://www.cnblogs.com/iiyiyi/p/5616080.html.数组范围开错了!!!然后2.31-1=0x7fffffff!=0x7f7f7f7f. 开始以 ...

  6. NOI2008 志愿者招募

    1061: [Noi2008]志愿者招募 Time Limit: 20 Sec  Memory Limit: 162 MBSubmit: 1859  Solved: 1169[Submit][Stat ...

  7. 线性规划||网络流(费用流):COGS 288. [NOI2008] 志愿者招募

    [NOI2008] 志愿者招募 输入文件:employee.in   输出文件:employee.out   简单对比 时间限制:2 s   内存限制:512 MB [问题描述] 申奥成功后,布布经过 ...

  8. 从[NOI2008志愿者招募]浅谈线性规划在网络流构图上的巧用

    首先来看一下题..http://www.lydsy.com/JudgeOnline/problem.php?id=1061 1061: [Noi2008]志愿者招募 Description 申奥成功后 ...

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

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

随机推荐

  1. 04 . Vue组件注册,组件间数据交互,调试工具及组件插槽介绍及使用

    vue组件 组件(Component)是 Vue.js 最强大的功能之一. 组件可以扩展 HTML 元素,封装可重用的代码. 组件系统让我们可以用独立可复用的小组件来构建大型应用,几乎任意类型的应用的 ...

  2. mysql之分区表

    1.分区表概述: 1.分区表的主要意义在于,对于表结构进行划分,不同的数据进入不同的分区中,以便于在查询过程中,只查找指定分区的数据,减少数据库扫描的数据量. 2.虽然从逻辑上看分区表是一张表,但是底 ...

  3. 使用 ffmpeg 命令直播推流

    CMD使用到的命令 D: -> 表示进入D盘cd /live -> 表示进入某个路径 执行条件 1.ffmpeg.exe 与视频放在同一目录下,纯英文目录2.执行以上命令进入到ffmpeg ...

  4. C# 9.0新特性详解系列之一:只初始化设置器(init only setter)

    1.背景与动机 自C#1.0版本以来,我们要定义一个不可变数据类型的基本做法就是:先声明字段为readonly,再声明只包含get访问器的属性.例子如下: struct Point { public ...

  5. ImportError: No module named 'chardet'

    1.使用requsets出现这个错误,ImportError: No module named 'chardet' 原因:requests依赖其他一些模块 解决:依次使用pip安装即可 pip ins ...

  6. Java 开发之 Lombok 必知必会

    转载链接地址:https://juejin.im/post/5cf3edf7e51d454f71439c79 1. 前言 在目前众多编程语言中,Java 语言的表现还是抢眼,不论是企业级服务端开发,还 ...

  7. scrapy学习之爬虫练习平台35

    前言 上一篇文章中爬取了爬虫练习平台的所有 ssr 网站,都是比较简单的,没有反爬措施,这次来爬一下后面的 spa 系列. 环境准备 这里沿用了上篇文章的环境和设置,就不重新搭建环境了. 开始爬取 s ...

  8. Spring MVC——项目的开发流程

    创建项目(IDEA下) 打开IDEA,我们开始创建一个简单的Spring MVC项目,流程如下: 这里要注意一下,我们是基于Maven开发项目,当然是要配置Maven环境的,如果大家之前从来没有配置过 ...

  9. 网站实现微信扫码登录 php

    微信开放平台账号一个,必须是商户,不然你也开不了 1.在开放平台创建应用,并设置回调地址(域名即可) 2.生成二维码,前端代码,用户扫码后会给你的回调地址发送code <span id=&quo ...

  10. 深度分析:理解Java中的多态机制,一篇直接帮你掌握!

    Java中的多态 1 多态是什么 多态(Polymorphism)按字面的意思就是"多种状态".在面向对象语言中,接口的多种不同的实现方式即为多态.用白话来说,就是多个对象调用同一 ...