AGC018C Coins 题解

模拟费用流。

传送门

题意:共 \(n=x+y+z\) 个人,每个人可以选择获得 \(a_i\) 个金币或 \(b_i\) 个银币或 \(c_i\) 个铜币。要选 \(x\) 个人拿金币,\(y\) 个人拿银币,\(z\) 个人拿铜币。问币数总和最大是多少。\(n\le 10^5\)

先建出费用流模型:把一个人的选择视作一个人流到了金币/银币/铜币的对应点。

给每个人抽象出一个点 \(p_i\),金银铜币抽象出三个点 \(g,s,b\),以及超源点 \(S\) 和超汇点 \(T\)
\(S\rightarrow p_i\),容量 \(1\) 费用 \(0\)\(g,s,b\rightarrow T\),容量 \(1\) 费用 \(0\)

\(p_i\rightarrow g,s,b\),容量 \(1\),费用 \(a_i/b_i/c_i\)

我们观察发现,这个费用流模型本质不同的增广路有十几种,根据经过 \(g,s,b\) 中的几个点分类讨论可以得出。直接模拟当然也可以,但是太复杂了,要考虑简化。
让每个人先取金币,令 \(B_i=b_i-a_i,C_i=c_i-a_i\)。则只要在 \(n\) 个人中选 \(y\) 个人取 \(B_i\)\(z\) 个人取 \(C_i\) 即可。费用流模型还是类似上面,但是右边就只剩两个点了。

这时本质不同的增广路就只剩四种:

  • \(S\rightarrow p_i\rightarrow s\rightarrow T\)。这种的收益是 \(B_i\)

  • \(S\rightarrow p_i\rightarrow b\rightarrow T\)。这种的收益是 \(C_i\)

  • \(S\rightarrow p_i\rightarrow s\rightarrow p_j\rightarrow b\rightarrow T\)。这种就是让一个已经选了 \(C_j\)\(j\) 转而选 \(B_j\),收益是 \(C_i+B_j-C_j\)

  • \(S\rightarrow p_i\rightarrow b\rightarrow p_j\rightarrow s\rightarrow T\)。这种的收益是 \(B_i+C_j-B_j\)

开四个堆分别维护即可。但鉴于这题调了 2.5h 且非常经典,所以再具体一点。

开四个堆 \(h_1,h_2,h_3,h_4\)

\(h_1\) 维护当前还未决定选 \(B_i\) 还是选 \(C_i\)的人中 \(B_i\) 的最大值及其编号\(h_2\) 维护还未决定的人中 \(C_i\) 的最大值及其编号。

\(h_3\) 维护所有目前选了 \(C_i\) 的人中 \(B_i-C_i\) 的最大值及其编号。\(h_4\) 维护所有目前选了 \(B_i\) 的人中 \(C_i-B_i\) 的最大值及其编号。

:虽然上面的收益是 \(C_i+B_j-C_j\),但是一定不要让 \(h_3\)\(h_4\) 维护 \(C_i+B_j-C_j\) 的最大值和对应的 \(i,j\)。第一是难写,第二是复杂度不对。

当我们想应用后两条增广路的时候,可以用 \(h_{2,3}\) 的堆顶共同求出 \(C_i+B_j-C_j\) 的最大值。

依次考虑应用每种增广路后会新增的影响。

这里一定要明确一下每种增广路对应的实际意义。

  • 应用 \(h_1\) 的堆顶:对应把某个还没选的选为 \(B_i\)

  • 应用 \(h_2\) 的堆顶:对应把某个还没选的选为 \(C_i\)

  • 应用 \(h_3+h_2\) 的堆顶:对应把某个还没选的选为 \(C_i\),然后把一个已经选为 \(B_j\) 的改成 \(C_j\)

  • 应用 \(h_4+h_1\) 的堆顶:对应把某个还没选的选为 \(B_i\),然后把一个已经选为 \(C_j\) 的改成 \(B_j\)

然后看每种增广路带来的可能性。

  • 应用 \(h_1\) 的堆顶:把某个还没选的选为 \(B_i\),会增加 \(h_3\) 的把某个 \(B_j\) 改成 \(C_j\) 的可能性。

  • 应用 \(h_2\) 的堆顶:把某个还没选的选为 \(C_i\),会增加 \(h_4\) 的把某个 \(C_j\) 改成 \(B_j\) 的可能性。

  • 应用 \(h_3+h_2\) 的堆顶:把某个还没选的选为 \(C_i\),然后把一个已经选为 \(B_j\) 的改成 \(C_j\)。这种操作会新增一个 \(B_i\) 和一个 \(C_i\),也就是说 \(h_3,h_4\) 的可能性都会增加。

  • 应用 \(h_4+h_1\) 的堆顶:把某个还没选的选为 \(B_i\),然后把一个已经选为 \(C_j\) 的改成 \(B_j\)。与 \(h_3\) 的类似。

最后还要注意一下:为了保证每次取出的堆顶都是有效的,在每次取堆顶之前,把 \(h_1,h_2\) 的所有在堆顶的已经选好了的元素都 pop 掉。

这里需要 pop 的原因是:在应用 \(h_3,h_4\) 的时候,会同时使用 \(h_1,h_2\) 的堆顶。(当然,在应用 \(h_3,h_4\) 的时候,顺便把额外使用的堆顶也 pop 掉同样可以)

\(h_3\)\(h_4\) 不需要 pop 的原因是没有任何一种增广路在应用时,会把 \(h_3,h_4\) 作为额外使用的堆顶。

顺路一提,这题有一个简化版CF730I:Olympiad in Programming and Sports

posted @ 2024-03-25 21:37  FLY_lai  阅读(20)  评论(0编辑  收藏  举报