uoj228

uoj228

UOJ 228 基础数据结构练习题

tag: 数据结构 线段树 区间更新 区间修改

这道题我们需要维护以下三种操作:(1)区间加、(2)区间开根、(3)区间和。

如果只有 (2) 和 (3) 就很好办了。 BZOJ 3211

通过 BZOJ 3211 我们知道,(2) 操作复杂度是 $O(m \log n)$ 的,因为每一次我们将一个数开根的话,在数据范围内最多需要 $7$ 次就能使一个数收敛到 $1$。

现在讨论 (1) 操作。在线段树上多维护两个标记:区间最大值 $mx​$ 和区间最小值 $mi​$。如果 $mx-mi \le 1​$ 时,将区间打标记,不向下更新。复杂度 $O((n+m) \log n \log \log n)​$。


利用势能分析法证明:

$|\sqrt {\lfloor{a} \rfloor} - \sqrt{\lfloor{b} \rfloor}| \le |\sqrt{\lfloor a-b\rfloor}|$

取等条件: $|a-b|\le1$

不满足取等条件时开一次根号。

定义势能函数:$f(x) = \Delta {A_i}$ 表示相邻两数的差。

最初势能是 $f(x)=O(nx)$;

每次区间加 会让势能增加 $x$ 分别在两端;

对一个满足条件的区间 开根 $\log \log n$ 次势能下降 $x$ 每次时间复杂度 $\log n$;

总势能 $(n+m)x$ ;

时间复杂度是 $(n+m) \log n \log\log n$。


其实很好感性的理解复杂度

想象数列是一排山峰高低不平 可以用 $O(Kn)$ 次开根把他们削平

区间加操作不过是把其中一段整体抬高

只要在这段两边多做$ \log \log n$ 次就能把他削平


怎么写呢?考虑如果一个区间最大值等于最小值(即全都相等),那么就可以直接开方,然后区间赋值。

否则,如果发现他们如果开方之后的减少的值是相同的,也就是说这种情况我们可以特判,变成区间减法操作,这样就可以保证复杂度。

为了降低编程复杂度,可以把区间赋值操作变成区间减法,也就是负数的区间减法,会方便很多。

另外,标记永久化 + 不下传,可以大大减小常数。

AC 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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
//#define NOSTDCPP
#ifndef NOSTDCPP
#include <bits/stdc++.h>
#else
#include <algorithm>
#include <bitset>
#include <cassert>
#include <complex>
#include <cstring>
#include <cstdio>
#include <deque>
#include <exception>
#include <fstream>
#include <functional>
#include <iomanip>
#include <iostream>
#include <istream>
#include <iterator>
#include <list>
#include <map>
#include <ostream>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <string>
#include <typeinfo>
#include <utility>
#include <valarray>
#include <vector>
#endif
# define RESET(_) memset(_, 0, sizeof(_))
# define RESET_(_, val) memset(_, val, sizeof(_))
# define fi first
# define se second
# define pb push_back
# define midf(x, y) ((x + y) >> 1)
# define DXA(_) ((_ << 1))
# define DXB(_) ((_ << 1) | 1)
using namespace std;
typedef long long ll;
typedef vector <int> vi;
typedef set <int> si;
typedef long double ld;
const int MOD = 1e9 + 7;
const int maxn = 100003 << 2;
const int maxm = 300009;
const double pi = acos(-1.0);
const double eps = 1e-6;
ll myrand(ll mod){return ((ll)rand() << 32 ^ (ll)rand() << 16 ^ rand()) % mod;}
template <class T>
inline bool scan_d(T & ret)
{
char c;
int sgn;
if(c = getchar(), c == EOF)return false;
while(c != '-' && (c < '0' || c > '9'))c = getchar();
sgn = (c == '-') ? -1 : 1;
ret = (c == '-') ? 0 : (c - '0');
while(c = getchar(), c >= '0' && c <= '9')
ret = ret * 10 + (c - '0');
ret *= sgn;
return true;
}
inline bool scan_ch(char &ch)
{
if(ch = getchar(), ch == EOF)return false;
while(ch == ' ' || ch == '\n')ch = getchar();
return true;
}
inline void out_number(ll x)
{
if(x < 0)
{
putchar('-');
out_number(- x);
return ;
}
if(x > 9)out_number(x / 10);
putchar(x % 10 + '0');
}
typedef struct
{
ll data, ma, mi, tag;
}node;
node tree[maxn];
int n, m;
void pushup(int p, int l, int r)
{
if(l < r)
{
tree[p].data = tree[DXA(p)].data + tree[DXB(p)].data + tree[p].tag * (r - l + 1);
tree[p].ma = max(tree[DXA(p)].ma, tree[DXB(p)].ma), tree[p].ma += tree[p].tag;
tree[p].mi = min(tree[DXA(p)].mi, tree[DXB(p)].mi), tree[p].mi += tree[p].tag;
}
}
void pre(int l, int r, int p)
{
tree[p].ma = -1;
tree[p].mi = 1e17;
tree[p].tag = 0;
if(l == r)
{
scan_d(tree[p].data);
tree[p].ma = tree[p].mi = tree[p].data;
return ;
}
int mid = midf(l, r);
pre(l, mid, DXA(p));
pre(mid + 1, r, DXB(p));
pushup(p, l, r);
}
void __add(int p, int l, int r, ll x)
{
tree[p].tag += x;
tree[p].ma += x;
tree[p].mi += x;
tree[p].data += x * (r - l + 1);
}
int l, r;
ll x;
void update_add(int nl, int nr, int p)
{
if(l <= nl && nr <= r)
{
__add(p, nl, nr, x);
return ;
}
int mid = midf(nl, nr);
if(l <= mid) update_add(nl, mid, DXA(p));
if(mid < r) update_add(mid + 1, nr, DXB(p));
pushup(p, nl, nr);
}
void update_sqrt(int nl, int nr, int p, ll tag)
{
if(l <= nl && nr <= r) //判断要不要往下更新 这里保证复杂度为 O(n log n log log n)
{
ll c1, c2;
c1 = (ll) sqrt(tree[p].mi + tag) + 1;
c2 = (ll) sqrt(tree[p].ma + tag);
ll cha;
if(tree[p].ma == tree[p].mi) // [nl, nr]所有值都相同 直接暴力更新 把开根转换成减法
{
cha = tree[p].mi + tag - (ll) sqrt(tree[p].mi + tag);
__add(p, nl, nr, -cha);
return ;
}
else if(tree[p].ma - tree[p].mi == 1 && c1 == c2) //[nl, nr]可以一起减下去
{
cha = tree[p].mi + tag - (ll) sqrt(tree[p].mi + tag);
__add(p, nl, nr, -cha);
return ;
}
}
int mid = midf(nl, nr);
if(l <= mid) update_sqrt(nl, mid, DXA(p), tree[p].tag + tag);
if(mid < r) update_sqrt(mid + 1, nr, DXB(p), tree[p].tag + tag);
pushup(p, nl, nr);
}
ll query(int nl, int nr, int p, ll tag)
{
if(l <= nl && nr <= r)
return tree[p].data + tag * (nr - nl + 1);
else
{
ll ans = 0;
int mid = midf(nl, nr);
if(l <= mid) ans += query(nl, mid, DXA(p), tag + tree[p].tag);
if(mid < r) ans += query(mid + 1, nr, DXB(p), tag + tree[p].tag);
return ans;
}
}
int main()
{
int q;
scan_d(n);
scan_d(m);
pre(1, n, 1);
while(m --)
{
scan_d(q);
scan_d(l);
scan_d(r);
switch(q)
{
case 1:
scan_d(x);
update_add(1, n, 1);
break;
case 2:
update_sqrt(1, n, 1, 0);
break;
case 3:
out_number(query(1, n, 1, 0));
puts("");
break;
default:
assert(false);
}
}
return 0;
}
/*
5 5
1 2 3 4 5
1 3 5 2
2 1 4
3 2 4
2 3 5
3 1 5
*/

未解决的问题

文章目录
  1. 1. UOJ 228 基础数据结构练习题
    1. 1.1. AC Code
  • 未解决的问题
  • {{ live2d() }}