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