[BZOJ3110][Zjoi2013]K大数查询

[BZOJ3110][Zjoi2013]K大数查询

试题描述

有N个位置,M个操作。操作有两种,每次操作如果是1 a b c的形式表示在第a个位置到第b个位置,每个位置加入一个数c
如果是2 a b c形式,表示询问从第a个位置到第b个位置,第C大的数是多少。

输入

第一行N,M
接下来M行,每行形如1 a b c或2 a b c

输出

输出每个询问的结果

输入示例

2 5
1 1 2 1
1 1 2 2
2 1 1 2
2 1 1 1
2 1 2 3

输出示例

1
2
1

数据规模及约定

N,M<=50000,N,M<=50000
a<=b<=N
1操作中abs(c)<=N
2操作中c<=Maxlongint

题解

线段树套线段树。第一维记录权值,第二维记录位置;具体意思指,对于第一维线段树上的一个节点 [L, R],它所指向的那颗线段树中记录的是每个位置包含了几个值在 [L, R] 范围内的数。

这题不能用线段树套平衡树(外层记录位置,内层维护权值)的原因:对于套在外面那一层的线段树是很难进行区间操作的,所以这题就需要换一个思路,把位置信息放在内层的线段树中。

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
using namespace std;

#define maxn 50010
#define maxnode 20000005
#define LL long long
int n;

int ToT, lc[maxnode], rc[maxnode];
LL addv[maxnode], sumv[maxnode];
int ql, qr, v;
void add(int& o, int L, int R) {
	if(!o) o = ++ToT;
//  printf("o: %d [%d, %d] [%d, %d]\n", o, L, R, ql, qr);
	if(ql <= L && R <= qr){ sumv[o] += R - L + 1; addv[o]++; return ; }
	int M = L + R >> 1;
	if(ql <= M) add(lc[o], L, M);
	if(qr > M) add(rc[o], M+1, R);
	sumv[o] = sumv[lc[o]] + sumv[rc[o]] + addv[o] * (R - L + 1);
	return ;
}
LL query(int o, int L, int R, LL Add) {
	if(!o) {
		int l = max(L, ql), r = min(R, qr);
		return Add * (r - l + 1);
	}
	if(ql <= L && R <= qr) return sumv[o] + Add * (R - L + 1);
	int M = L + R >> 1; LL ans = 0;
	if(ql <= M) ans += query(lc[o], L, M, Add + addv[o]);
	if(qr > M) ans += query(rc[o], M+1, R, Add + addv[o]);
	return ans;
}

int rt[maxn<<3];
void update() {
	int o = 1, L = 1, R = n << 1 | 1;
	while(L < R) {
		add(rt[o], 1, n);
		int M = L + R >> 1, ls = o << 1, rs = ls | 1;
		if(v <= M) R = M, o = ls;
		else L = M + 1, o = rs;
	}
	return add(rt[o], 1, n);
}
int query() {
	int o = 1, L = 1, R = n << 1 | 1;
	while(L < R) {
		int M = L + R >> 1, ls = o << 1, rs = ls | 1;
		LL rsz = query(rt[rs], 1, n, 0);
//		printf("%d %d %lld %lld\n", L, R, rsz, v);
		if(v <= rsz) L = M + 1, o = rs;
		else R = M, o = ls, v -= rsz;
	}
	return L;
}

int main() {
	int q, tp;
	scanf("%d%d", &n, &q);
	while(q--) {
		scanf("%d%d%d%d", &tp, &ql, &qr, &v);
		if(tp == 1) v += n + 1, update();
		if(tp == 2) printf("%d\n", query() - n - 1);
	}
	
	return 0;
}

这是卡常神题啊!!!我调了半天最后把内层线段树改成静态懒标记(就是标记不下传,据说这个叫标记永久化?)才通过的。。。

posted @ 2017-01-18 19:16  xjr01  阅读(188)  评论(0编辑  收藏  举报