本篇翻译自G - Dynamic Scheduling Editorial by en_translator

+

这个问题有多种解决方案(包括利用拟阵性质的解决方案)。

+

在本文中,我们介绍了一种将问题形式化为成本流问题并对其进行优化的方法。

+

使用线段树分治法进行预处理

+

首先,对于离线查询(其中查询都是预先给出的)的问题,可以使用一种称为线段树分治的技术来简化查询。

+
    +
  • 虽然很典型,但这个查询似乎没有命名,因此我参考 OI Wiki 给出了一个名字。
  • +
+

该问题中提出的问题可以重新表述如下。

+
+

T有一组任务,最初为空。处理以下查询:

+
    +
  • 将任务插入到集合中(插入查询
  • +
  • 从集合中删除一个任务(删除查询
  • +
  • 打印子问题的答案
  • +
+
+

简单来说,它要求在数据结构中进行插入和删除查询。

+

一般来说,管理集合的数据结构几乎不支持删除查询,除非数据结构简单得多。例如,不相交集合并集不支持删除查询。

+

使用线段树分治技巧,可以将删除查询变成撤消查询。即:

+
+

有一组任务,最初为空。处理以下查询:

+
    +
  • 将任务插入到集合中(插入查询
  • +
  • 最后插入的任务从集合中删除(撤消查询
  • +
  • 打印子问题的答案
  • +
+
+

与删除查询不同,撤消查询通常可以通过管理堆栈中与先前状态的差异来处理,而不会增加计算复杂性。例如,不相交集并集可以通过此技巧轻松支持撤消查询。

+

因此,应用线段树分治法,我们不再需要支持删除查询,而只需关注插入查询,在某些情况下可以大大简化问题。

+

现在我们介绍线段树分治法的流程。

+

首先,我们准备一个线段树来管理时间轴。这里的时间轴是指将处理第一个查询的时间分配给时间 00 ,将处理第二个查询的时间分配给时间 11 ,依此类推。线段树的每个节点都存储一个管理元素的向量。

+

接下来,我们对输入进行适当的预处理,计算每个要插入到集合中的元素存在的时间跨度。就像处理针对线段树的线段更新查询一样,将元素插入到与 [l,r)[l, r) 对应的节点中。

+

插入后,从线段树的根开始DFS(深度优先搜索)。在DFS中,当你进入一个节点时,将元素插入到该节点的向量中;当你退出一个节点时,撤消这些元素。在这个 DFS 中,到达第 ii 个叶子时的集合对应于应该处理第 ii 个查询时的集合状态。

+

总体复杂度为 O(QlogTf(n))\mathrm{O}(Q \log T f(n)) ,其中 QQ 是查询次数, TT 是最大时间, O(f(n))\mathrm{O}(f(n)) 是插入和撤消查询的复杂度。

+

将形式化视为成本流问题

+

现在我们已经应用了线段树分治法,问题现在归结为以下问题。

+
+

有一组任务,最初为空。以下类型的进程 O((N+Q)logQ)\mathrm{O}((N + Q) \log Q)

+
    +
  • 将新任务插入到集合中
  • +
  • 删除插入到集合中的最后一个任务
  • +
  • 打印子问题的答案
  • +
+
+

目前,我们忽略了可以通过适当的过程以相同复杂度处理的撤消查询,如上所述。此外,假设为每个插入查询计算最优解,现在我们只有一种查询:

+
+

有一组任务,最初为空。以下类型的进程 O((N+Q)logQ)\mathrm{O}((N + Q) \log Q)

+
    +
  • 向集合中插入新任务,并打印子问题的答案
  • +
+
+

我们现在将尝试解决这个问题。

+

该问题可以描述为成本流问题,如下所示。

+
+

有一个图,其顶点有 (2N+2)(2N+2) 个,标记为 S,A1,,AN,B1,,BN,TS, A_1, \dots, A_N, B_1, \dots, B_N, T
+边的添加方式如下。

+
    +
  • 每对 (i,j)(i, j)jDij \leq D_i 的边 AiBjA_i \to B_j 具有容量 11 和成本 00
  • +
  • BjTB_j \to T 具有容量 11 和成本 00
  • +
  • 边缘 TST \to S 具有容量 \infty 和成本 00
  • +
+
+

查询表示如下。

+
+
    +
  • 插入任务 ii 时,添加一条边 SAiS \to A_i ,容量为 11 ,成本为 Pi-P_i ,并找出乘以 1-1 的最小成本流通量。
  • +
+
+

在本次讨论的剩余部分,我们将尝试解决这个问题。
+在处理查询之前,我们将始终获得最优成本流的残差图(以便残差图永远不会有负循环)。然后,考虑查询向图中添加边 SAiS \to A_i 时的行为,我们实际上有以下事实:

+
+

引理

+

添加查询时,最多执行一次以下操作即可从残差图中消除一个负循环:如果存在负循环,则删除成本最小的负循环。

+
+

(证明)如果添加边不会产生负循环,则无需执行任何操作即可保持最优性,因此无需执行任何操作。现在我们假设出现了一个新的负循环。

+

由于之前的残差图没有负循环,新的负循环包含添加的边。(因此,消除负循环的新流量为 11 。)让 CC 成为具有最小成本的负循环。此外,用 GG 表示添加之前的图,用 GG' 表示添加边并将流量 11 推至 CC 之后的图。足以表明 GG' 没有负循环。

+

假设 GG' 有一个负循环,用 CC' 表示。

+

新边的容量为 11 ,因此 CC' 不包含新边。此外,假设 CC' 的边仅由 GG 中的边组成,违反了 GG 的最优性。因此,它包含通过推动 CC 中的流添加的反向边。

+

这里,考虑由 CC 中流量为 11 的边部分和 CC' 中流量为 11 的边部分组成的图。那么,该图由以下三个边集组成:

+
    +
  1. CC 中的一条边和 CC' 中包含的其反向边组成的一对或多对。
  2. +
  3. GG 中有零个或多个循环。
  4. +
  5. 由添加的边和 GG 中的边组成的零个或一个循环。
  6. +
+

此图中边的成本总和为负数,因为 CCCC' 都是负循环。然而,1. 中边的成本加起来为 00 ,而 GG 中的循环的成本为 00 或更高,因为它们包含在 GG 中。因此,如果 3. 不存在循环,则 1.、2. 和 3. 的成本总和为负数,这是矛盾的。如果存在这样的循环 CC'' ,用 w(c)w(c) 表示循环 cc 的成本,则我们有

+

w(C)+w(C)=(1的贡献)+(2的贡献)+(3的贡献)w(C)\begin{aligned} w(C) + w(C') &= (1的贡献) + (2的贡献) + (3的贡献) \geq w(C'') \end{aligned} +

+

w(C)w(C)w(C)gt;w(C)\begin{aligned} w(C) \geq w(C'') - w(C') > w(C'') \end{aligned} +

+

因此,证明了 CC'' 的成本小于 CC ,这与循环 CC 的成本最小性相矛盾。

+

因此,假设 CC' 的存在会导致矛盾,因此 GG' 没有负循环,形成最优成本流的残差图。(证明结束)

+

现在我们已经克服了这个问题最困难的部分(作者为此付出了很大努力),但以这种方式重新表述或引理本身对优化没有帮助。(已知找到具有最大成本的负边是 NP 难问题)。相反,我们使用图的属性来快速模拟成本流。

+

使用任务集模拟成本流

+

直接处理成本流不会导致优化,因此我们将成本流上的操作改写为针对任务集的操作。

+

首先,构造的图是二分图匹配的流表示。因此,很明显,流为 SAiS \to A_i 的集合 ii 对应于子问题中在截止日期前可以完成的任务集。因此,残差图可以对应于子问题中在截止日期前完成的一组任务。在下文中,我们用 XX 表示在截止日期前完成的任务集,并保留 XX 而不是残差图。在查询样式中,它表示如下。

+
+

有一组任务,最初为空。

+

XX 成为当前任务集的最佳解决方案中可以在截止期限之前完成的任务集。

+

以下类型的流程 O((N+Q)logQ)\mathrm{O}((N + Q) \log Q)
+Process O((N+Q)logQ)\mathrm{O}((N + Q) \log Q) of the following types:

+
    +
  • 将新任务 II 插入到集合中,同时更新 XX 。打印 XX 中任务的总奖励。
  • +
+
+

现在我们详细考虑“同时更新 XX ”部分。

+

让我们首先考虑如何使用 XX 表示负循环的移除。当残差图包含负循环时,负循环采用以下形式之一:

+
    +
  • SAiBABTSS \to A_i \to B_* \to A_* \to \dots \to B_* \to T \to S
  • +
  • SAiBABAjSS \to A_i \to B_* \to A_* \to \dots \to B_* \to A_j \to S
  • +
+

这两个循环可以分别改写为针对任务集的操作如下:

+
    +
  • 插入任务 iiXX
  • +
  • XX 中删除 jj ,并插入任务 ii
  • +
+

反过来,可以证明如果其中一个操作可行,则存在相应的负边。因此,两个操作的存在等同于两种负循环的存在。

+

(证明概要)我们证明前者是相互对应的。(后者也一样。)通过合并 XX 的残差图和 X+{i}X + \lbrace i \rbrace 的残差图并删除自环,可以检查它由 SAiBBTSS \to A_i \to B_* \to \dots \to B_* \to T \to S 和零个或多个成本- 00 循环组成,前者可以证明由 GG 中的边组成。(证明结束)

+

因此,负循环及其消除已通过针对任务集的操作重新表述。

+

接下来,让我们通过针对 XX 的操作重新表述更新残差图的过程。根据引理,成本流过程中更新残差图的过程可以通过以下方式实现:

+
+
    +
  • 如果有负边,则删除成本最小的一条负边。
  • +
+
+

“最低成本”部分的具体描述如下:

+
+
    +
  • 如果存在形式为 SAiBBTSS \to A_i \to B_* \to \dots \to B_* \to T \to S 的负循环,则推动流程。
  • +
  • 否则,如果存在形式为 SAiBBAjSS \to A_i \to B_* \to \dots \to B_* \to A_j \to S 的负循环,则将流推送到成本最小的负循环。
  • +
  • 否则就什么也不做。
  • +
+
+

这相当于针对任务集进行如下操作:

+
+
    +
  • 如果可以将任务 ii 添加到 XX ,请添加它。
  • +
  • 否则,如果存在 jj 并且满足 Pi>PjP_i \gt P_j ,并且可以通过从 XX 中删除任务 jj 来插入任务 ii ,则选择具有最小 PjP_jjj ,从 XX 中删除 jj ,然后插入任务 ii
  • +
  • 否则不做任何事。
  • +
+
+

通过上面的讨论,现在将形式化为成本流问题,并针对任务集进行操作。

+

现在操作不是很抽象。要解决的问题表示为查询形式,如下所示:

+
+

有一组任务,最初为空。

+

XX 成为当前任务集的最佳解决方案中可以在截止期限之前完成的任务集。

+

以下类型的流程 O((N+Q)logQ)\mathrm{O}((N + Q) \log Q)

+
    +
  • 将新任务 II 插入到集合中,同时更新 XX 。打印 XX 中任务的总奖励。
  • +
  • XX执行以下操作。 +
      +
    • 如果可以将任务 ii 添加到 XX ,请添加它。
    • +
    • 否则,如果存在 jj 并且满足 Pi>PjP_i \gt P_j ,并且可以通过从 XX 中删除任务 jj 来插入任务 ii ,则选择具有最小 PjP_jjj ,从 XX 中删除 jj ,然后插入任务 ii
    • +
    • 否则不做任何事。
    • +
    +
  • +
  • 打印 XX 中任务的总奖励。
  • +
+
+

虽然路途遥远,但我们已经接近目标了!

+

使用赫尔婚姻定理进行优化

+

为了处理上述查询,让我们构建如下数据结构。

+
+

XX 成为一组任务。如果 XX 中的所有任务都可以在子问题的截止日期前完成,则集合 XX 被称为有效。构建一个支持以下查询的数据结构。

+
    +
  • 将任务 ii 插入至 XX
  • +
  • XX 中删除任务 ii
  • +
  • 确定 XX 是否有效。
  • +
  • 如果 XX 无效,则找到 ii ,使得“从 XX 中删除 ii 使其有效”,并且最小值为 PiP_i
  • +
+
+

尝试一次处理所有类型的查询太困难了,所以让我们首先考虑第一类和第三类查询:

+
+
    +
  • 插入任务 iiXX
  • +
  • 确定 XX 是否有效。
  • +
+
+

为了处理这两种类型的查询,请考虑以某种方式维护有关任务的信息。回顾子问题的要求, XX 的有效性可以表示为二分图上的匹配,如下所示:

+
+

UU 成为 XX 中包含的顶点集,让 VV 成为与日期对应的顶点集。对于 uUu \in UvVv \in V ,如果日期 uu 在任务 uu 的截止时间当天或之前,则添加一条边 uvu \to v 。然后,当且仅当存在覆盖 UU 的匹配时, XX 才有效。

+
+

当涉及到确定匹配问题的存在性(即确定是否存在覆盖二分图某一部分的所有顶点的匹配)时,就轮到霍尔婚姻定理了。让我们以某种方式使用霍尔婚姻定理来表述这个条件。

+

对于 AUA \subseteq U ,令 Γ(A)\Gamma(A) 为与 AA 相邻的顶点集。霍尔婚姻定理表述如下:

+
+

赫尔婚姻定理

+

存在覆盖 UU 中顶点的匹配当且仅当:

+

AU,AΓ(A).\forall A \subseteq U, |A| \leq |\Gamma(A)|. +

+
+

这个不等式是关于 UU 侧的集合的条件,但对其进行变形可得到 VV 侧的条件。(证明省略。可以通过考虑不等式的语义来理解。)

+
+

存在覆盖 UU 中顶点的匹配当且仅当:

+

BV,{uuU,Γ(u)B}B.\forall B \subseteq V, |\lbrace u \vert u \in U, \Gamma(u) \subseteq B \rbrace| \leq |B|. +

+
+

通过像这样推导出关于 VV 侧的集合的条件,可以利用此问题的性质。

+

InI_nVV 的子集,由第 11 天、第 22 天、 \dots 天和第 nn 天组成。对于每个任务 ii ,从相应顶点到 IDiI_{D_i} 中包含的顶点都有一条边。由此可见,我们只需将上述不等式中的 I1,I2,,INI_1, I_2, \dots, I_N 视为 VV 。(证明结束)

+

B=InB = I_n 上的不等式可以变形如下:

+

{uuU,Γ(u)In}In    {uuU,Dun}n    n{uuU,Dun}0. \begin{aligned} &|\lbrace u \vert u \in U, \Gamma(u) \subseteq I_n \rbrace| \leq |I_n| \\ \iff &|\lbrace u \vert u \in U, D_u \leq n \rbrace| \leq n \\ \iff &n - |\lbrace u \vert u \in U, D_u \leq n \rbrace| \geq 0. \end{aligned} +

+

根据迄今为止的事实,可以构建处理查询的数据结构如下。

+
+

让 $$f(n) = n - |\lbrace u \vert u \in U, D_u \leq n \rbrace|.$$

+

我们不再管理 XX ,而是使用支持段添加和段最小化的惰性段树来管理 f(1),f(2),,f(n)f(1), f(2), \dots, f(n)

+
    +
  • 将任务 ii 插入到 XX 时,将 1-1 添加到 f(Di),f(Di+1),,f(N)f(D_i), f(D_i + 1), \dots, f(N)
  • +
  • 由于 XX 当且仅当 f(1),f(2),,f(N)f(1), f(2), \dots, f(N) 全部为非负时才有效,因此检查 min(f(1),f(2),,f(N))0\min(f(1), f(2), \dots, f(N)) \geq 0 就足够了。
  • +
+
+

因此,对于仅给出任务添加和有效性检查查询的情况,问题已经解决。

+

我们已经解释了关键部分,因此我们省略了细节,但忽略的第二类和第四类查询也可以以某种方式处理。简而言之,第二类查询可以以与第一类相同的方式处理,第四类查询可以通过利用std::set或点更新段最小线段树来处理。对于所有查询,都可以轻松实现撤消查询。

+

因此,管理 XX 的数据结构已在每次查询中花费 O(logN)\mathrm{O}(\log N) 的时间构建完成。因此,整个问题可以在总共 O((N+Q)logQlogN)\mathrm{O}((N + Q) \log Q \log N) 的时间内解决,这已经足够快了。

+