题解 P7619 [COCI2011-2012#2] RASPORED

Posted Dragon_Skies

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了题解 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 \\leq N,C \\leq 2 \\times 10^5 ,0 \\leq L_i \\leq 10^5 ,1 \\leq T_i \\leq 10^5\\)

Solution

首先考虑如果没有修改,就一次询问怎么做。先推推式子:

假设我们已经通过某种方法给这 \\(N\\) 个居民排好了顺序,我们按照顺序给第 \\(1,2,3,\\cdots N-1,N\\) 个居民烤披萨。

因为我们要让小费最多,所以肯定不会存在烤了一会又休息一会什么的。

对于第 \\(i\\) 个居民,能得到的小费的式子是:

\\[L_i-\\sum_{j=1}^i T_i \\]

如果 \\(P_i\\) 是负数,就代表要支付费用。

可以根据题目来理解:前面的 \\(L_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_i\\) 出现的次数,可以发现: \\(T_j\\) 会在 \\(\\forall i \\geq j\\)\\(i\\) 中减一次,这样的 \\(i\\)\\(n-j+1\\) 个,就相当于一共减了 \\(T_j \\times (n-j+1)\\)

所以最终的式子就是:

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

我们再回过头来看怎么确定烤的顺序,注意到前面 \\(\\sum_{i=1}^n L_i\\) 与顺序无关,所以只用考虑后面这个部分。

又因为我们要让结果最大,所以就要让后面这一部分最小,因为 \\(n-i+1\\) 随着 \\(i\\) 的增大而减小,所以对于越小的 \\(i\\) ,应该让 \\(T_i\\) 尽可能小。

于是就可以按照 \\(T_i\\) 从小到大排序,就可以得到最大小费了。


下面设 \\(ans1=\\sum_{i=1}^n L_i,ans2=\\sum_{i=1}^nT_i\\times(n-i+1)\\) 。最终要输出的值就是 \\(ans1-ans2\\)

但是这一题有修改,怎么办?

修改 \\(L_i\\) 很好办,直接让 \\(ans1\\) 减掉原来的,再加上现在的就行。

修改 \\(T_i\\) 稍微麻烦一点:首先把修改变成删掉一个数,再插入一个数。

观察一下式子的变化,原来的 \\(ans2\\) 是:

\\[T_1\\times n+\\cdots+T_{i-1}\\times(n-i+2)+T_{i}\\times(n-i+1)+T_{i+1}\\times(n-i)+\\cdots++T_n \\]

假如说这时要删掉 \\(T_i\\) 这个数字,再次观察一下新的 \\(ans2\\) 的式子:

\\[T_1\\times (n-1)+\\cdots+T_{i-1}\\times(n-i+1)+T_{i+1}\\times(n-i)+\\cdots+T_n \\]

这个式子比较难理解,大概是这么来的:

  • 前面的 \\(n\\) 全部变成了 \\(n-1\\) 是因为删除一个数以后数字个数减去了 \\(1\\)
  • 对于 \\(i\\) 后面的数字,这里以 \\(i+1\\) 为例,原本是 \\(n-(i+1)+1=n-i-1+1=n-i\\) ,现在 \\(n\\) 减去了 \\(1\\) ,比 \\(T_{i+1}\\) 小的数字也少了一个,式子就变成了 \\((n-1)-(i+1-1)+1=n-1-i+1=n-i\\) ,所以和原来一样。

发现这里对于 \\(i \\in [1,i-1]\\) 每个 \\(T_i\\) 乘的数字都减去了 \\(1\\) ,对于 \\(i \\in [i+1,n]\\) ,每个 \\(T_i\\) 乘的数字不变。同时 \\(ans2\\) 还要减去 \\(T_i \\times (n-i+1)\\)

如果是插入的话是一样的,只不过从下面的式子推到上面这个式子。

插入的结论是:对于 \\(i\\in[1,i-1]\\) ,每个 \\(T_i\\) 乘的数字都加上了 \\(1\\) ,对于 \\(i\\in[i+1,n]\\) ,每个 \\(T_i\\) 乘的数字不变。同时 \\(ans2\\) 还要加上 \\(T_i \\times (n-i+1)\\)

当然对于插入和删除操作,我们都需要"动态排序",即实时更新下标。

于是我们就需要一个数据结构,支持:

  • 插入一个数。
  • 删除一个数。
  • 查询比一个数小的数的个数。
  • 查询比一个数小的数的和。

可以用平衡树或者树状数组,这里用的 Splay 来实现。

几个细节:

  • 在求和的时候要开 long long ,不然可能会炸。
  • 记得修改的时候同时更新原来的数组(方便下次操作)
  • 如果要插入/删除的数有很多个,那么直接插入/删除最靠左的一个,可以用几个数据算一算,会发现这是对的,证明我就不写了。

代码如下( Splay 是真的慢):

#include <cstdio>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <iostream>
typedef long long LL;
using namespace std;
inline int read() {
    int num = 0 ,f = 1; char c = getchar();
    while (!isdigit(c)) f = c == \'-\' ? -1 : f ,c = getchar();
    while (isdigit(c)) num = (num << 1) + (num << 3) + (c ^ 48) ,c = getchar();
    return num * f;
}
const int N = 2e5 + 5 ,INF = 1e6;
struct Splay {
    struct node {
        int ch[2] ,fa ,val ,siz ,cnt; LL sum;
        node () : fa(0) ,val(0) ,siz(0) ,cnt(0) ,sum(0) {ch[0] = ch[1] = 0;}
    }t[N]; int root ,tot;
    Splay () : root(0) ,tot(0) {}
    inline void update(int now) {
        t[now].siz = t[t[now].ch[0]].siz + t[t[now].ch[1]].siz + t[now].cnt;
        t[now].sum = t[t[now].ch[0]].sum + t[t[now].ch[1]].sum + (LL)t[now].cnt * t[now].val;
    }
    inline void rotate(int x) {
        int y = t[x].fa ,z = t[y].fa; bool k = t[y].ch[1] == x;
        t[x].fa = z; t[z].ch[t[z].ch[1] == y] = x;
        t[y].ch[k] = t[x].ch[k ^ 1]; t[t[x].ch[k ^ 1]].fa = y;
        t[x].ch[k ^ 1] = y; t[y].fa = x;
        update(y); update(x);
    }
    inline void splay(int x ,int goal) {
        while (t[x].fa != goal) {
            int y = t[x].fa ,z = t[y].fa;
            if (z != goal) {
                if ((t[z].ch[1] == y) == (t[y].ch[1] == x)) rotate(y);
                else rotate(x);
            }
            rotate(x);
        }
        if (goal == 0) root = x;
    }
    inline void insert(int val) {
        int now = root ,fa = 0;
        while (now && t[now].val != val) fa = now ,now = t[now].ch[val > t[now].val];
        if (now) t[now].cnt++;
        else {
            now = ++tot;
            t[now].val = t[now].sum = val;
            t[now].siz = t[now].cnt = 1;
            t[now].fa = fa;
            t[fa].ch[val > t[fa].val] = now;
        }
        splay(now ,0);
    }
    inline void build() {insert(-INF); insert(INF);}
    inline void find(int val) {
        int now = root;
        while (t[now].val != val && t[now].ch[val > t[now].val]) now = t[now].ch[val > t[now].val];
        splay(now ,0);
    }
    inline int findnext(int val ,int k) {
        find(val);
        if (t[root].val < val && k == 0) return root;
        if (t[root].val > val && k == 1) return root;
        int now = t[root].ch[k];
        while (t[now].ch[k ^ 1]) now = t[now].ch[k ^ 1];
        return now;
    }
    inline void remove(int val) {
        int l = findnext(val ,0) ,r = findnext(val ,1);
        splay(l ,0) ,splay(r ,l);
        int now = t[r].ch[0];
        if (t[now].cnt >= 2) t[now].cnt-- ,splay(now ,0);
        else t[r].ch[0] = t[now].fa = 0 ,update(r) ,update(l);
    }
    node& operator [] (int index) {return t[index];}
}t;
int n ,a[N] ,nums[N] ,L[N] ,C;
LL ans1 ,ans2;
signed main() {
    n = read(); C = read(); t.build();
    for (int i = 1; i <= n; i++) {
        L[i] = read(); a[i] = read();
        nums[i] = a[i]; ans1 += L[i]; t.insert(a[i]);
    }
    sort(nums + 1 ,nums + n + 1);
    for (int i = 1; i <= n; i++) ans2 += (LL)nums[i] * (n - i + 1);
    printf("%lld\\n" ,ans1 - ans2);
    while (C--) {
        int x = read() ,l = read() ,T = read();
        ans1 = ans1 - L[x] + l; L[x] = l;
        t.find(a[x]);
        int now = t.root;
        ans2 -= t[t[now].ch[0]].sum + INF;
        //这里加上正无穷的原因是在平衡树里面我插入了负无穷,所以比一个数小的数一定含有负无穷
        //所以 ans2 就要加上正无穷。
        ans2 -= LL(n - t[t[now].ch[0]].siz + 1) * a[x];
        t.remove(a[x]);
        a[x] = T;
        t.insert(T);
        now = t.root;
        ans2 += t[t[now].ch[0]].sum + INF;
        ans2 += LL(n - t[t[now].ch[0]].siz + 1) * a[x];
        printf("%lld\\n" ,ans1 - ans2);
    }
    return 0;
}

以上是关于题解 P7619 [COCI2011-2012#2] RASPORED的主要内容,如果未能解决你的问题,请参考以下文章

[SinGuLaRiTy] COCI 2011~2012 #2

COCI 2011-2012 setnja

[COCI2011-2012#5] POPLOCAVANJE

P4596 [COCI2011-2012#5] RAZBIBRIGA

P4611 [COCI2011-2012#7] TRAMPOLIN

题解Dvoniz [COCI2011]