P3168 任务查询系统(主席树 + 扫描线)

题目链接: 任务查询系统

大致题意

解题思路

这里放一道类似的题目. 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;
}

END

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

逍遥Fau

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值