题解 P7619 [COCI2011-2012#2] RASPORED

题目描述

Link

Mirko 的比萨店是城里最好的,镇上所有的居民每天午餐都吃比萨饼。而且 Mirko 的送货服务很快,送货时间可以忽略不计。但是 Mirko 只有一个小烤箱,一次只能烤一个比萨饼。

我们将城里的 \(N\) 个居民从 \(1\)\(N\) 编号,他们计划吃午餐的时间为 \(L_i\),Mirko 需要为他们烘焙比萨的所需时间为 \(T_i\)

如果一个居民在他计划吃午餐时间的前 \(K\) 个时间单位收到了他的比萨饼,那么 Mirko 会得到 \(K\) 元小费。相应地,如果一个居民在他计划吃午餐时间的后 \(K\) 个时间单位才收到了他的比萨饼,那么 Mirko 必须向居民付款 \(K\) 元。如果比萨饼准时送到,Mirko 不会得到小费,但是也不用付任何费用。

请你帮助 Mirko 安排一天的比萨烘焙顺序,使得他一天赚取的总小费最大

注意:

  1. 一天从时间单位 \(0\) 开始,你可以认为这一天是无限长的。
  2. 居民们有时会改变他们的 \(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\) 个居民上获取多少小费(如果是负数就是要向这个居民支付费用),那么:

\[c_i=l_i-\sum_{j=1}^i t_i \]

稍微解释一下这个式子:前面这一项是指当前居民预定的时间,后面这一项是指烘焙好这个居民的披萨时刻,按照题目的要求稍微模拟一下就能够得到这个式子。

那么最终答案就是:

\[\sum_{i=1}^n c_i = \sum_{i=1}^n (l_i-\sum_{j=1}^i t_i) \]

\[=\sum_{i=1}^nl_i - \sum_{i=1}^n\sum_{j=1}^i t_i \]

从第一行到第二行的变化是用了加法的交换律和结合律。

对于后面这一项,考虑继续推:对于 \(t_j\) 这个数来说,它在 \(i=j,j+1,j+2\cdots n\) 的时候都会被算一次,一共就被算了\(n-j+1\) 次,因此原式等于:

\[\sum_{i=1}^n l_i - \sum_{i=1}^n t_i \times(n - i + 1) \]

然后再来考虑怎么确定烘焙的顺序:首先前面这一项无论怎么排都是不变的,关键是后面这一项。

因为最终的结果要最大,在前面一项不变的基础上,后面一项肯定越小越好,那么对于越小的 \(i\)\(n-i+1\) 会越大,所以我们要尽量让这个 \(i\) 所对应的 \(t_i\) 越小。

于是归纳一下,就可以得到:将 \(t\) 整个数组从小到大排序,这个式子会得到最大值。


下面为了写的字数少一点,定义:

\[ans1=\sum_{i=1}^n l_i ,ans2 = \sum_{i=1}^n t_i \times (n-i+1) \]

然后因为有修改,所以这里不能简单地用下标了,这里定义 \(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\) 是:

\[\sum_{i=1}^n b_i \times (n-pos_i + 1) \]

删掉了 \(b_x\) 之后,分两种情况讨论:

  1. 对于 \(\forall b_i < b_x\)\(pos_i\) 是不会变的,但是 \(n\) 因为删除了一个数减去了 \(1\) ,现在的 \(c_i\) 是:

\[c_i= b_i \times (n - pos_i + 1 - 1)= b_i \times (n - pos_i) \]

可以发现,\(b_i\) 乘的数字减去了 \(1\) ,所以对于 \(ans2\) 就是减去了所有小于 \(b_x\) 的数的和,即 \(\sum_{b_i<b_x}b_i\)

  1. 对于 \(\forall t_i \geq t_x\)\(pos_i\) 因为删除了一个数减去了 \(1\)\(n\) 也减去了 \(1\) ,现在的 \(c_i\) 是:

\[c_i=b_i\times((n-1)-(pos_i-1)+1)=b_i\times(n-1-pos_i+1+1) \]

\[=b_i \times (n-pos_i+1) \]

这一部分的数和原来是一样的,对 \(ans2\) 没有影响。

当然删除 \(b_x\) 也让答案减掉了 \(b_x\times(n-pos_x+1)\)

因此删除 \(b_x\) 的结论就是\(ans2\) 减去了所有小于 \(b_x\) 的数的和,同时也减去了 \(b_x\) 的贡献,用数学的式子表示就是:

\[ans2\gets ans2 - b_x\times(n-pos_x+1)-\sum_{b_i<b_x}b_i \]

插入也是基本一样的,限于篇幅这里直接给结论:

\(ans2\) 加上了所有小于 \(b_x\) 的数的和,同时也加上了 \(b_x\) 的贡献用数学的式子表示就是:

\[ans2\gets ans2 + b_x\times(n-pos_x+1)+\sum_{b_i<b_x}b_i \]

到这里就基本明了了,现在我们需要一个数据结构,支持:

  1. 插入/删除。
  2. 查询比一个数小的数的个数(用于获取 \(pos_x\) )。
  3. 查询比一个数小的数的和。

可以用平衡树或者树状数组,最终的时间复杂度都是 \(\mathcal{O}(n \log n)\)

这里我两种都写了,但是 Splay 的时间是树状数组的 \(4\) 倍,常数是真的大。

对了,不要忘了一开始对于原始的数据要求一遍答案,然后输出的值其实是上面的 \(ans1-ans2\)

代码放在云剪贴板里


Update on 2021.6.12 :重构了这篇文章,同时加入树状数组的写法。

Update on 2021.6.15 :更新了 \(\LaTeX\) 的错误。

posted @ 2021-06-11 21:40  recollector  阅读(83)  评论(0编辑  收藏  举报