BZOJ 1901(动态区间第K小)

传送门
树套树什么的真的头晕死了。


题目大意

给你一个序列$a_{i\in [1,n]}$,要求单点修改,区间询问第$k$小值。

解析

如不没有修改,直接一棵主席树就可以了。
有修改也没关系,整体二分就可以了。
有修改的话,主席树肯定没办法了,因为它存的是前缀,修改的话时间直接GG。
但是我们仍然可以运用主席树的思想。
我们要做的,其实就是维护一个区间内的$cnt=[a_{i\in [l,r]} \le val]$,就可以查询排名、找到$k$大值了。
那如果我们还是建出来$n$棵权值线段树,每次对操作暴力$O(n\ log_2n)$的更新,就可以维护,但是显然时间GG。
但是我们注意到一个事情,我们要做的东西类似下面的操作:

1
2
3
4
5
6
7
8
9
10
void update(int l, int r)
{
for (int i = l ; i <= r; i++) tree_add(i);
}
int query(int l, int r)
{
int res = 0;
for (int i = l ; i <= r; i++) res += get_sum(i);
return res;
}

这个东西可以用什么操作?
树状数组!
如果我们采用类似主席树的方法,只是在权值线段树外面套一层树状数组,那我们单次操作的复杂度就成功成$O(n\ log_2n)$降低到了可以接受的$O(log_2^2n)$了。

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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
#define DREP(i, s, e) for(int i = s; i >= e ;i--)
#define REP(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 <bits/stdc++.h>

using namespace std;
const int maxn = 1e4 + 10, maxq = 1e4 + 10;

template <typename T> inline 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;
}

int n, m, q, a[maxn], b[maxn + maxq];
int to(int val) {return lower_bound(b + 1, b + 1 + m, val) - b;}

bool type[maxq];
int x[maxq], y[maxq], z[maxq];

struct node *null;
struct node
{
node *ls, *rs;
int sum;
node() : ls(null), rs(null), sum(0) {}
}*c[maxn];

#define mid (l + r >> 1)

void update(node *pre, node *&p, int l, int r, int pos, int num)
{
node temp = *pre;
p = new node();
p -> ls = temp.ls;
p -> rs = temp.rs;
p -> sum = temp.sum + num;
if (l == r) return;
else if (pos <= mid) update(temp.ls, p -> ls, l, mid, pos, num);
else update(temp.rs, p -> rs, mid + 1, r, pos, num);
}
void add(int x, int pos, int num = 1)
{
while (x <= n)
{
if (c[x] == NULL) c[x] = new node();
update(c[x], c[x], 1, m, pos, num);
x += x & -x;
}
}
void update(int l, int r, int pos, int num = 1) {add(l, pos, num);add(r + 1, pos, num * -1);}

int lsz, rsz;
node *LLL[maxn], *RRR[maxn];

int query(int l, int r, int k)
{
if (l == r) return l;
int x = 0;
REP(i, 1, rsz) x += RRR[i] -> ls -> sum;
REP(i, 1, lsz) x -= LLL[i] -> ls -> sum;
if (x >= k)
{
REP(i, 1, lsz) LLL[i] = LLL[i] -> ls;
REP(i, 1, rsz) RRR[i] = RRR[i] -> ls;
return query(l, mid, k);
}
else
{
REP(i, 1, lsz) LLL[i] = LLL[i] -> rs;
REP(i, 1, rsz) RRR[i] = RRR[i] -> rs;
return query(mid + 1, r, k - x);
}
}

int main()
{
#ifdef CraZYali
freopen("1901-new.in", "r", stdin);
freopen("1901-new.out", "w", stdout);
#endif
null = new node();
null -> ls = null -> rs = null;null -> sum = 0;
cin >> n >> q;
REP(i, 1, n) a[i] = b[++m] = read<int>();
REP(i, 1, q)
{
char cc = getchar();
while (cc != 'Q' && cc != 'C') cc = getchar();
x[i] = read<int>();y[i] = read<int>();
if (cc == 'Q') type[i] = 1, z[i] = read<int>();
else b[++m] = y[i];
}
sort(b + 1, b + 1 + m);
m = unique(b + 1, b + 1 + m) - b - 1;

REP(i, 1, n) add(i, to(a[i]));
REP(i, 1, q)
if (type[i])
{
lsz = rsz = 0;
for (int now = x[i]-1; now > 0;now -= (now & -now)) LLL[++lsz] = c[now];
for (int now = y[i]; now > 0;now -= (now & -now)) RRR[++rsz] = c[now];
//提前提取需要查询的树状数组的结点
printf("%d\n", b[query(1, m, z[i])]);
}
else
{
add(x[i], to(a[x[i]]), -1);
add(x[i], to(a[x[i]] = y[i]));
}
return 0;
}

P.S

做题都做得感觉脑子有问题了。
对于这种会让人脑子有问题的东西,有的时候不要想太多,直接把一些东西当成工具就好,不然真的头疼。

评论

Your browser is out-of-date!

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

×