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。