【ybt金牌导航4-6-5】【luogu P3168】任务查询系统

任务查询系统

题目链接:ybt金牌导航4-6-5 / luogu P3168

题目大意

有一些数会出现,它会在一段时间内出现。
然后多个强制在线的询问,问某个时间中前 k 大的数的和。

思路

我们考虑一个时刻对于它前面的时刻有什么变化。

多了一些数,少了一些数。
而每个数整个过程中只会多一次少一次。
那我们就可以用主席树,让每个时刻都弄一个线段树。
然后每次就复制之前的,在修改多的和少的。

容易看到权值需要离散化。
(我手打离散化不知道为什么不行,调了一个下午,就用了自带的)

然后询问就在当前时刻查询。

还有如果查询的时候排名大于数个数,就输出当前所有数的和。

代码

#include<queue>
#include<cstdio>
#include<algorithm>
#define ll long long
#define y first
#define x second

using namespace std;

struct node {
	int l, r, c, s;
}t[200001 << 6];
pair <int, int> p[200001];
int m, n, x, y, z, k, rt[200001];
int tot, maxval, b[200001];
ll pre = 1;

int abss(int x) {
	if (x < 0) return -x;
	return x;
}

void build(int &now, int l, int r) {
	now = ++tot;
	if (l == r) return ;
	
	int mid = (l + r) >> 1;
	build(t[now].l, l, mid);
	build(t[now].r, mid + 1, r);
	
	return ;
}

int copypoint(int bef) {
	int re = ++tot;
	t[re] = t[bef];
	return re;
}

int insert(int bef, int l, int r, int pl, int x) {
	int now = copypoint(bef);
	
	if (l == r) {
		t[now].c += x;
		t[now].s += 1ll * x * b[pl];
		return now;
	}
	
	int mid = (l + r) >> 1;
	if (pl <= mid) t[now].l = insert(t[bef].l, l, mid, pl, x);
		else t[now].r = insert(t[bef].r, mid + 1, r, pl, x);
	
	t[now].c = t[t[now].l].c + t[t[now].r].c;
	t[now].s = t[t[now].l].s + t[t[now].r].s;
	return now;
}

ll get_sum(int now, int l, int r, int rnk) {
	if (l == r) return 1ll * t[now].s / t[now].c * rnk;//记得这里要乘上的不一定是这个数的个数,而是你现在的 rnk
	
	int mid = (l + r) >> 1;
	if (rnk <= t[t[now].l].c) return get_sum(t[now].l, l, mid, rnk);
		else return t[t[now].l].s + get_sum(t[now].r, mid + 1, r, rnk - t[t[now].l].c);
}

int main() {
//	freopen("read.txt", "r", stdin);
	
	scanf("%d %d", &m, &n);
	for (int i = 1; i <= m; i++) {
		scanf("%d %d %d", &p[(i << 1) - 1].y, &p[i << 1].y, &p[(i << 1) - 1].x);
		p[i << 1].y++;
		p[i << 1].x = -p[(i << 1) - 1].x;
		b[i] = p[(i << 1) - 1].x;
	}
	
	sort(b + 1, b + m + 1);//离散化数的大小
	maxval = unique(b + 1, b + m + 1) - b - 1;
	
	build(rt[0], 1, maxval);
	
	sort(p + 1, p + (m << 1) + 1);
	int tmp = 1;
	
	for (int i = 1; i <= (m << 1); i++) {//每个时间点建一个线段树(主席树来搞)
		while (tmp < p[i].y) {
			rt[tmp + 1] = rt[tmp];
			tmp++;
		}
		if (tmp == m + 1) break;
		int pp = lower_bound(b + 1, b + maxval + 1, abss(p[i].x)) - b;
		rt[tmp] = insert(rt[tmp], 1, maxval, pp, p[i].x > 0 ? 1 : -1);
	}
	
	for (int i = 1; i <= n; i++) {
		scanf("%d %d %d %d", &x, &k, &y, &z);
		k = 1 + (1ll * k * pre + y) % z;
		if (k >= t[rt[x]].c) pre = t[rt[x]].s;//超过当前的数的个数,直接是全部的和
			else pre = get_sum(rt[x], 1, maxval, k);//在对应的线段树查询
		printf("%lld\n", pre);
	}
	
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值