题解 P7619 [COCI2011-2012#2] RASPORED
题目描述
Mirko 的比萨店是城里最好的,镇上所有的居民每天午餐都吃比萨饼。而且 Mirko 的送货服务很快,送货时间可以忽略不计。但是 Mirko 只有一个小烤箱,一次只能烤一个比萨饼。
我们将城里的 \(N\) 个居民从 \(1\) 到 \(N\) 编号,他们计划吃午餐的时间为 \(L_i\),Mirko 需要为他们烘焙比萨的所需时间为 \(T_i\)。
如果一个居民在他计划吃午餐时间的前 \(K\) 个时间单位收到了他的比萨饼,那么 Mirko 会得到 \(K\) 元小费。相应地,如果一个居民在他计划吃午餐时间的后 \(K\) 个时间单位才收到了他的比萨饼,那么 Mirko 必须向居民付款 \(K\) 元。如果比萨饼准时送到,Mirko 不会得到小费,但是也不用付任何费用。
请你帮助 Mirko 安排一天的比萨烘焙顺序,使得他一天赚取的总小费最大。
注意:
- 一天从时间单位 \(0\) 开始,你可以认为这一天是无限长的。
- 居民们有时会改变他们的 \(T_i,L_i\)。
\(1 \leq N,C \leq 2 \times 10^5 ,0 \leq L_i \leq 10^5 ,1 \leq T_i \leq 10^5\)
Solution
写在前面:题目描述中的大写字母这里都用小写字母代替,对于 \(a-b\) 这样的式子,下面的前面一项指的是 \(a\) ,后面一项指的是 \(b\) 。
首先考虑没有修改,即只询问一次:容易发现赚取的小费的多少只与烘焙顺序有关,因此我们可以假设已经按照某种顺序给这 \(n\) 个居民排好了烘焙顺序,可以推推式子:
设 \(c_i\) 表示能从第 \(i\) 个居民上获取多少小费(如果是负数就是要向这个居民支付费用),那么:
稍微解释一下这个式子:前面这一项是指当前居民预定的时间,后面这一项是指烘焙好这个居民的披萨时刻,按照题目的要求稍微模拟一下就能够得到这个式子。
那么最终答案就是:
从第一行到第二行的变化是用了加法的交换律和结合律。
对于后面这一项,考虑继续推:对于 \(t_j\) 这个数来说,它在 \(i=j,j+1,j+2\cdots n\) 的时候都会被算一次,一共就被算了\(n-j+1\) 次,因此原式等于:
然后再来考虑怎么确定烘焙的顺序:首先前面这一项无论怎么排都是不变的,关键是后面这一项。
因为最终的结果要最大,在前面一项不变的基础上,后面一项肯定越小越好,那么对于越小的 \(i\) , \(n-i+1\) 会越大,所以我们要尽量让这个 \(i\) 所对应的 \(t_i\) 越小。
于是归纳一下,就可以得到:将 \(t\) 整个数组从小到大排序,这个式子会得到最大值。
下面为了写的字数少一点,定义:
然后因为有修改,所以这里不能简单地用下标了,这里定义 \(pos_i\) 表示比 \(t_i\) 小的数字个数 \(+1\) (也就是排好序之后这个数的下表), \(ans2\) 就可以表示为 \(\sum_{i=1}^n t_i \times(n - pos_i + 1)\)。
再来考虑有修改的情况:
首先我们需要两个数组 \(a,b\) 来分别存储当前的 \(l\) 和 \(t\) 。
如果让 \(a_x \gets k\) 的话,那么直接更新 \(ans1\) (\(ans1\gets ans1 - a_i + k\)),同时更新 \(a\) 数组即可。
如果让 \(b_x \gets k\) 的话,我们可以把这个操作看成先从原来的数组里面删掉 \(b_x\) ,再插入一个数字 \(k\) ,接下来对插入和删除对于 \(ans2\) 的变化分开来计算。
对于删除,原先的 \(ans2\) 是:
删掉了 \(b_x\) 之后,分两种情况讨论:
- 对于 \(\forall b_i < b_x\) , \(pos_i\) 是不会变的,但是 \(n\) 因为删除了一个数减去了 \(1\) ,现在的 \(c_i\) 是:
可以发现,\(b_i\) 乘的数字减去了 \(1\) ,所以对于 \(ans2\) 就是减去了所有小于 \(b_x\) 的数的和,即 \(\sum_{b_i<b_x}b_i\) 。
- 对于 \(\forall t_i \geq t_x\) ,\(pos_i\) 因为删除了一个数减去了 \(1\) , \(n\) 也减去了 \(1\) ,现在的 \(c_i\) 是:
这一部分的数和原来是一样的,对 \(ans2\) 没有影响。
当然删除 \(b_x\) 也让答案减掉了 \(b_x\times(n-pos_x+1)\) 。
因此删除 \(b_x\) 的结论就是:\(ans2\) 减去了所有小于 \(b_x\) 的数的和,同时也减去了 \(b_x\) 的贡献,用数学的式子表示就是:
插入也是基本一样的,限于篇幅这里直接给结论:
\(ans2\) 加上了所有小于 \(b_x\) 的数的和,同时也加上了 \(b_x\) 的贡献用数学的式子表示就是:
到这里就基本明了了,现在我们需要一个数据结构,支持:
- 插入/删除。
- 查询比一个数小的数的个数(用于获取 \(pos_x\) )。
- 查询比一个数小的数的和。
可以用平衡树或者树状数组,最终的时间复杂度都是 \(\mathcal{O}(n \log n)\) 。
这里我两种都写了,但是 Splay 的时间是树状数组的 \(4\) 倍,常数是真的大。
对了,不要忘了一开始对于原始的数据要求一遍答案,然后输出的值其实是上面的 \(ans1-ans2\) 。
Update on 2021.6.12 :重构了这篇文章,同时加入树状数组的写法。
Update on 2021.6.15 :更新了 \(\LaTeX\) 的错误。