「CQOI2015」任务查询系统题解

题解 #

本题要求的是在某一时刻前 k 小的优先级的和,所以我们不妨以优先级(要先离散化)为下标建立可持久化线段树(主席树),也就是说从任一根节点出发得到的是某一时刻正在运行的所有任务。这样一个任务可以被拆分成两个事件:

  1. 在 S 秒时加入正在运行的任务的集合(也就是在主席树上对应的优先级上 +1)
  2. 在 E+1 秒时从正在运行的任务的集合中移除(也就是在主席树上对应的优先级上 -1)

我们把所有事件按时间顺序排序并依次更新主席树,并记录对于每个时刻,该时刻前最新版本的线段树的根节点,查询时就从该根节点出发。

线段树要维护两个信息,一个是区间内正在运行任务的数量,一个是正在运行的任务的优先级之和。

值得注意的是查询的是单一时刻的信息,不像求区间第 k 小时要两个区间信息相减,所以写起来也简单一点。

一个非常容易错的地方是查询是当走到叶子节点时,如果该优先级的任务数量大于当前的 k 值,不能直接加上叶子节点中的优先级之和,要先除以任务数量再乘 k 值。

代码 #

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
 
struct PST {
    int n, tot = 0;
    struct node {
        int lc, rc, cnt;
        ll sum;
    };
    vector<node> t;
    vector<int> roots; // left child, right child
    PST(int n_) : n(n_), t(n << 6), roots(1) {
        build(0, n - 1, roots[0]);
    }
#define lc(rt) t[rt].lc
#define rc(rt) t[rt].rc
    void pushup(int rt) {
        t[rt].sum = t[lc(rt)].sum + t[rc(rt)].sum;
        t[rt].cnt = t[lc(rt)].cnt + t[rc(rt)].cnt;
    }
    void build(int l, int r, int &rt) {
        rt = ++tot;
        if (l == r) return;
        int mid = (l + r) >> 1;
        build(l, mid, lc(rt));
        build(mid + 1, r, rc(rt));
        pushup(rt);
    }
    void update(int pos, int dcnt, int dsum, int l, int r, int old, int &rt) {
        rt = ++tot;
        t[rt] = t[old];
        if (l == r) {
            t[rt].cnt = t[old].cnt + dcnt;
            t[rt].sum = t[old].sum + dsum;
            return;
        }
        int mid = (l + r) >> 1;
        if (pos <= mid) update(pos, dcnt, dsum, l, mid, lc(old), lc(rt));
        else update(pos, dcnt, dsum, mid + 1, r, rc(old), rc(rt));
        pushup(rt);
    }
    int update(int pos, int dcnt, int dsum) { // return the root of the new version
        int new_root;
        update(pos, dcnt, dsum, 0, n - 1, roots.back(), new_root);
        roots.push_back(new_root);
        return new_root;
    }
    ll query(int v, int l, int r, int k) {
        if (l == r)
            return (t[v].cnt > k ? t[v].sum / t[v].cnt * k : t[v].sum);
        int mid = (l + r) / 2, x = t[lc(v)].cnt;
        ll sum = t[lc(v)].sum;
        if (k <= x) return query(lc(v), l, mid, k);
        return sum + query(rc(v), mid + 1, r, k - x);
    }
    ll query(int v, int k) { return query(v, 0, n - 1, k); }
};
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n, q;
    cin >> n >> q;
    vector<tuple<int, int, int>> tasks(n);
    vector<int> p; // 所有的优先级,用于离散化
    for (auto &[s, e, pp] : tasks) {
        cin >> s >> e >> pp;
        p.push_back(pp);
    }
    sort(begin(p), end(p));
    p.erase(unique(begin(p), end(p)), end(p)); // 离散化
    vector<pair<int, int>> events;
    for (auto [s, e, pp] : tasks) {
        int id = lower_bound(begin(p), end(p), pp) - begin(p);
        // 两个事件,用优先级的正负来表示加入或者删除
        events.emplace_back(s, id + 1);
        events.emplace_back(e + 1, -id - 1);
    }
    sort(begin(events), end(events));
    PST tr(size(p));
    vector<int> roots(n + 1); // root[i] 代表 i 时刻前最新的线段树的版本
    roots[0] = 1;
    for (auto [time, id] : events) {
        if (id > 0) {
            roots[time] = (tr.update(id - 1, 1, p[id - 1]));
        } else {
            id = -id - 1;
            roots[time] = (tr.update(id, -1, -p[id]));
        }
    }
    for (int i = 1; i <= n; i++)
        // 对于没有事件发生的时刻 i,其最新的版本为上一时刻的最新版本
        if (!roots[i]) roots[i] = roots[i - 1];
    ll pre = 1;
    while (q--) {
        int x, a, b, c;
        cin >> x >> a >> b >> c;
        auto k = 1 + (a * pre + b) % c;
        auto res = tr.query(roots[x], k);
        cout << res << '\n';
        pre = res;
    }
}