题解 #
本题要求的是在某一时刻前 k 小的优先级的和,所以我们不妨以优先级(要先离散化)为下标建立可持久化线段树(主席树),也就是说从任一根节点出发得到的是某一时刻正在运行的所有任务。这样一个任务可以被拆分成两个事件:
- 在 S 秒时加入正在运行的任务的集合(也就是在主席树上对应的优先级上 +1)
- 在 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;
}
}