BZOJ 3110 [ZJOI2013 K大数查询]

传送门
哎呀这题刚了一上午没刚出来,下午发现少写$5$个字符,浑身难受。


题目大意

有$N$个位置,$M$个操作。
操作有$2$种:

  • $1\ a\ b\ c$ 在第$a$个位置到第$b$个位置,每个位置加入一个数$c$。
  • $2\ a\ b\ c$ 询问从第$a$个位置到第$b$个位置,第$c$大的数是多少。

解析

整体二分?早就弃坑了。
我记得我之前的计划中有一个线段树套线段树来着,这个题就是一个树套树。
外层搞一个权值线段树,内层套一个区间。

权值线段树

定义

权值线段树

如图,这就是一个权值线段树,它对应的序列为${1,3,3,5,4,2}$。
每个结点保存的信息有三个$[l,r],cnt$,意味着在原序列中,$cnt=\sum_{i=1}^n [a_i \in [l,r]]$。
注意到两点:

  1. 权值线段树只保存数字的出现次数,不考虑序列的顺序。
  2. 一般来讲,权值线段树保存的序列值需要离散化(空间问题)

所以说,上面那颗线段树不开到$[1,6]$也没有关系,直接离散化(这个序列没必要)后开成$[1,5]$也可以。
同样,如果序列是${1,2,5,4,3,3}$,权值线段树开成$[1,6]$,也会长成上图的样子。

找排名为$K$的数

直接从根节点开始找,重复以下过程。

  1. $K \le now -> LeftSon -> cnt$$,说明这个数一定不超过当前区间最大值,那么$$now = now -> LeftSon$$。
  2. $K > now -> LeftSon -> cnt$,说明这个数一定超过当前区间最大值,那么$K = K - now -> LeftSon -> cnt$,同时$now = now -> RightSon$。
  3. 找到一个确定的数为止。

像$FHQ\ TREAP$啊,还有一些数据结构都是这样找的。

权值线段树套区间

差不多的东西,只是权值线段树里的东西从数变成了区间而已。
内层套的区间,也就是每次加进来的值在那些地方。

标记永久化

一个小小的技巧。
因为结点都被拆开了,所以下传标记不太好办。
怎么搞呢?
不下传不就可以了吗,更新的时候一起更新,查询的时候不仅返回$sum$$,而且返回$$tag$$在区间的作用就可以了。
没懂就看代码吧。

正题

对于每个询问,先查找右边的总数$cnt$,如果右边总数大于等于$c$就在右子树查询第$c$大的,如果不够就在左子树查询第$c-cnt$大的。
应该挺好懂的。
最后,线段树动态开点

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
#define  REP(i, s, e) for (int i = s; i <= e; i++)
#define DREP(i, s, e) for (int i = s; i >= e; i--)
#define DEBUG fprintf(stderr, "Passing [%s] in Line %d\n", __FUNCTION__, __LINE__)

#define chkmax(a, b) a = max(a, b)
#define chkmin(a, b) a = min(a, b)

#include <cstdio>
#include <iostream>

using namespace std;

template <typename T> T read()
{
T ans = 0, p = 1;
char c = getchar();
while (!isdigit(c))
{
if (c == '-') p = -1;
c = getchar();
}
while (isdigit(c))
{
ans = ans * 10 + c - 48;
c = getchar();
}
return ans * p;
}

#define mid (l + r >> 1)
#define lson p -> ls, l, mid
#define rson p -> rs, mid + 1, r

int n, m;

struct node_inside
{
node_inside *ls, *rs;
int sum, tag;
node_inside() : sum(0), tag(0), ls(NULL), rs(NULL) {}
};
int query(node_inside *x, bool flag = 1) {return x != NULL ? x -> sum * flag + x -> tag * (flag ^ 1) : 0;}
void update(node_inside *&p, int l, int r, int L, int R)
{
if (p == NULL) p = new node_inside();
if (L == l && r == R) p -> tag++;
else
if (L > mid) update(rson, L, R);
else if (R <= mid) update(lson, L, R);
else
{
update(lson, L, mid);
update(rson, mid + 1, R);
}
p -> sum += R - L + 1;
}
int query(node_inside *&p, int l, int r, int L, int R)
{
if (p == NULL) return 0;
if (L == l && r == R) return query(p);
int res = (R - L + 1) * query(p, 0);
if (R <= mid) return query(lson, L, R) + res;
else if (L > mid) return query(rson, L, R) + res;
else return query(lson, L, mid) + query(rson, mid + 1, R) + res;
}
struct node_outside
{
node_inside *rt;
node_outside *ls, *rs;
node_outside() : ls(NULL), rs(NULL), rt(NULL){}
};

node_outside *T;
void update(node_outside *&p, int l, int r, int L, int R, int pos)
{
if (p == NULL) p = new node_outside();
update(p -> rt, 1, n, L, R);
if (l == r) return;
if (pos <= mid) update(lson, L, R, pos);
else update(rson, L, R, pos);
}
int query(node_outside *&p, int l, int r, int L, int R, int pos)
{
if (l == r) return l;
int cnt = p -> rs == NULL ? 0 : query(p -> rs -> rt, 1, n, L, R);
if (cnt >= pos) return query(rson, L, R, pos);
else return query(lson, L, R, pos - cnt);
}

int main()
{
#ifdef CraZYali
freopen("3110.in", "r", stdin);
freopen("3110.out", "w", stdout);
#endif
cin >> n >> m;
while (m --> 0)
{
int opt(read<int>()), a(read<int>()), b(read<int>()), c(read<int>());
if (opt == 1) update(T, 1, n, a, b, c);
else printf("%d\n", query(T, 1, n, a, b, c));
}
return 0;
}

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×