3.14考试总结

别的不会,就会T1

\(n\) 个工人,第 \(i\) 个工人的初始效率值为 \(a_i\) 。 有 \(m\) 个工作,第 \(i\) 个工作需要恰好 \(k_i\) 个工人来完成。 你可以分配工人去完成工作,一个工人可以参加多个工作,但不能重复参加一个工作。当一个工人参加一个工作时,你会获得等同于这个工人当前效率值的收益,然后这个工人效率值减 \(1\)。 你想知道你完成所有任务所能获得的最大收益。

\(1\le n, m \le 5 \times 10^5\)
\(1\le k \le 10^6\)

考场上想了一堆乱搞做法,包括正解,但是思路一直有点歪

我们首先给所有的工作排个序,然后我们可以把它们的效率搞成一个柱状图

显然的一个结论是我们每次要改的是前 \(k\) 大个

那么我们会想,改完以后会怎么样

对于这样一串数:\(6~6~5~5~5~4\)

如果我们令 \(k_i = 3\) 那么我们的最终序列应该是 \(5~5~3~4~4~3\)
发现本来我们的数列是递减的这么一搞就不是了

但是我们绘成柱状图以后就会更好处理

这是刚开始的柱状图
那么我们如果让前面的每一段进行操作的话,肯定是横着砍一刀,那么就无法保证永远不递增

但是我们还有其他的办法,对于这样一串数,我们完全可以不用这种方式砍,我们考虑,对于前面的两个6,你怎么砍都不会有影响,但是对于这个五来说,就一定会有影响,我们考虑能不能找一个数代替5?答案是肯定的,因为我们发现,数列末尾的那个五减一是不影响答案的,因此我们就可以把这一段的第三个数减一改成第五个数减一,这样就能继续保证我们的数列单调递减,每次我们只要搞到前面的 \(k_i\) 个就能统计对答案的贡献,同时只要找到最后一段的后 \(k\) 个就能保证数列递减

那么问题就转化得很显然了,我们要找到最后一段的左端点和右端点,还得有区间减法

我们考虑用树状数组或者线段树来维护这些数,用线段树先记下来各个和,然后每次我们先让 \(ans+= [i-b[i]+1, n]\) 这一段的值,然后找到最后一个点 \(i-b[i]+1\) 的值,然后二分查找这一段值的左右端点 \(l,r\),先修改和这一段无关的区间 \([r,n]\),对于后面一段本来应该是让我们修改从 \([i-b[i] + 1,r]\),接下来我们只需要修改从左端点开始的 \([l,r-(i-b[i]+1) + l]\) 即可

但是我们发现写两个线段树常数有亿点点大,过不去,我们把第二个维护点值的线段树改成树状数组即可

inline int find(int val, int type) {
    int l = 1, r = n; res = 0;
    if (!type) {
        while (l <= r) {
            int mid = (l + r) >> 1;
            if (query(mid) >= val) res = mid, r = mid - 1;
            else l = mid + 1;
        }
    }
    else {
        while (l <= r) {
            int mid = (l + r) >> 1;
            if (query(mid) <= val) res = mid, l = mid + 1;
            else r = mid - 1;
        }
    }
    return res;
}

signed main(){
    // system("fc 1.out 2.out");
#ifndef ONLINE_JUDGE
    freopen("1.in", "r", stdin);
    freopen("2.out", "w", stdout);
#endif
    read(n);
    rep (i, 1, n) read(a[i]);
    read(m);
    rep (i, 1, m) read(b[i]);
    sort(a + 1, a + 1 + n);
    sort(b + 1, b + 1 + m);
    rep (i, 1, n) add (a[i] - a[i - 1], i);
    build(1, 1, n);
    rep (i, 1, m) {
        int p = n - b[i] + 1;
        ans += query(1, p, n, 1, n);
        int val = query(p);
        int l = find(val, 0), r = find(val, 1);
        update(1, l, r - p + l, 1, n, -1);
        update(1, r + 1, n, 1, n, -1);
        add(-1, l), add(1, l + r - p + 1);
        add(-1, r + 1), add(1, n + 1);
    }
    write(ans, '\n');
    return 0;
}

核心就这一段,但是我调了好长时间,原因是线段树写挂了哈哈!我宣布个事,我是个啥比

posted @ 2022-03-14 20:15  RevolutionBP  阅读(72)  评论(0编辑  收藏  举报