题目链接: 任务查询系统
大致题意
…
解题思路
这里放一道类似的题目. Shooting
主席树 + 扫描线
这里先说一下主席树 + 扫描线的用法.
我们把题目中给定的三元组假想成, 在坐标轴x轴上方, 有一条线段平行于x轴, 位于[l, r], 距x轴的距离为c. 那么题目中相当于每次询问出一个pos, 表示当前位于pos处, 问你当前位置上方距离x轴最近的k条线段的总距离和是多少.
对于这种模型, 我们就可以采用如下方法: 将每条线段用差分的思路拆分成点的形式, 并且以x轴上的每个坐标值(1, 2, 3, 4, 5, …)建立权值线段树.
这样对于一条线段, 相当于在root[l]版本处+c, 在root[r+1]版本处-c. 那么当我们位于root[pos]版本时, 即为当前点的情况.
需要注意的是, 建立线段树时, 对于第i个版本, 我们可能对其有多次操作(即有多个线段拆分处的点都在i位置有影响), 也可能对于当前版本, 没有点对其有影响.
如果是多个点对其有影响, 我们可以重复建树, 第一次以root[i-1]版本建树, 之后以root[i]版本建树.
如果没有点对其有影响, 则当前版本的线段树应该完全继承前一版本. 否则会导致当前节点的线段树为空树.
那么在进行查询的时候, 我们直接对于root[pos]的线段树进行查询即可(类似于第k小数的查询, 只不过是求和).
本题需要额外注意的是, 如果到了叶节点, 我们需要判断是否当前节点包含的线段个数大于查询的k.
AC代码
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using namespace std;
typedef long long ll;
const int N = 1E5 + 10;
vector<int> v(1, -0x3f3f3f3f);
int find(int x) { return lower_bound(v.begin(), v.end(), x) - v.begin(); }
struct LINE { //存储线段
int x, val;
bool operator< (const LINE& t) const { return x < t.x; }
}; vector<LINE> line;
struct node {
int l, r;
int cou; ll val; //线段条数, 线段总价值
}t[N << 6];
int root[N], ind;
int build(int a, int c, int tl, int tr, int p) { //不要尝试去动态修改树!!! 一般空间是够的
int x = ++ind; t[x] = t[p];
t[x].cou += (c > 0 ? 1 : -1);
t[x].val += c;
if (tl == tr) return x;
int mid = tl + tr >> 1;
if (a <= mid) t[x].l = build(a, c, tl, mid, t[p].l);
else t[x].r = build(a, c, mid + 1, tr, t[p].r);
return x;
}
ll ask(int k, int tl, int tr, int x) {
if (tl == tr) {
int temp = min(k, t[x].cou); //我们只要前k个, 而当前区间多于k个, 需要取min
return 1ll * temp * v[tl]; //注意反离散化.
}
int mid = tl + tr >> 1;
int cou = t[t[x].l].cou;
if (cou >= k) return ask(k, tl, mid, t[x].l);
return t[t[x].l].val + ask(k - cou, mid + 1, tr, t[x].r);//注意计算左区间贡献.
}
int main()
{
int n, m; cin >> n >> m;
rep(i, n) {
int l, r, c; scanf("%d %d %d", &l, &r, &c);
line.push_back({ l, c }), line.push_back({ r + 1, -c }); //线段变点
v.push_back(c); //离散化
}
sort(line.begin(), line.end());
sort(v.begin(), v.end()); v.erase(unique(v.begin(), v.end()), v.end());
int len = v.size() - 1;
int pline = 0;
rep(i, m) {
//记得先得到前一个版本, 因为坐标是连续的. 如果当前i位置没有对应的线段, 则会导致空树
root[i] = root[i - 1];
while (pline < line.size() and line[pline].x == i) {
int a = find(abs(line[pline].val)), c = line[pline].val;
root[i] = build(a, c, 1, len, root[i]);
pline++;
}
}
ll pre = 1; //题目要求
rep(i, m) {
int x, a, b, c; scanf("%d %d %d %d", &x, &a, &b, &c);
int k = 1 + (1ll * a * (pre % c) + b) % c; //题目要求计算k的方式
ll res = ask(k, 1, len, root[x]);
printf("%lld\n", pre = res);
}
return 0;
}