莫队二次离线
综述
有时我们会遇见一些题目,这些题目看起来很适合使用莫队算法处理,但是他们的单次转移并非是
的,这时直接使用莫队即使调整块长,也会导致复杂度不正确。
此时,如果每次转移对答案的贡献可以进行差分,我们就可以将这些转移拆开离线下来,使用其它算法批量处理。
我们用
表示
关于
产生的贡献。
如我们在将当前区间
扩展到
时,我们要求的是
。如果可以差分,我们可以将其写成
,其中第一项我们可以对于每个
都预处理出来,后一项我们可以把每个这样的项都离线存到对应的
上,然后从小到大枚举并扫描线处理。其它几个转移的方向也都可以类似地处理。
这一在莫队这个离线算法上,将转移再次离线处理的算法叫做莫队二次离线。
我们结合实际题目来理解。
例题
Luogu P5047 [Ynoi2019 模拟赛] Yuno loves sqrt technology II
给你一个长为
的序列
,
次询问,每次查询一个区间的逆序对数。
数据范围:
,
。
直接莫队每次转移至少是
的,观察可以发现我们每次转移要求的信息是「一个数在某个区间内的排名」,而这一信息关于区间可以差分,因此考虑二次离线。
我们将
拓展到
,要求的是 在
区间内有多少比
大的数。 我们用
表示
区间内比
大的数,
表示
区间内比
小的数。则答案的变化量可以写作
。
同理
缩小至
的变化量可以写作
;
拓展到
可以写作
,
缩小至
可以写作
。
对于这些式子中的
和
,我们可以使用树状数组提前
预处理出来;对于其余的
和
项,我们将
离线存在
进行批量处理。
这里有一个优化空间的技巧:我们发现处理每个询问时,离线到
上的
都是连续的一段,因此我们不需要把每次移动都存下,只需要存下调整的一段即可。这样我们的空间可以从总次数
下降到询问数
。
现在我们来处理我们二次离线下来的问题:向一个集合中加入数,查询一个数在集合中的排名。莫队总共移动端点的次数是
的,而数组总共只有
的长度,因此我们考虑使用
插入、
查询的值域分块解决这个问题。
至此,我们在
的时间复杂度和
的空间复杂度下解决了此题。
最后值得注意的是,我们求得的是每次的答案变化量而非答案本身,需要对其进行前缀和才能得到最终的答案。
示例代码
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 | #include <algorithm>
#include <cmath>
#include <cstring>
#include <iostream>
#include <vector>
constexpr int MAX = 1e5 + 5;
constexpr int MAXX = 4e2 + 5;
constexpr int MX = 1e5;
typedef long long ll;
int n, m, sz, _sz;
int a[MAX], b[MAX], bl[MAX], st[MAXX], ed[MAXX], b1[MAXX], b2[MAX], p1[MAX],
p2[MAX];
ll ans[MAX];
struct N {
int l, r, id, x, y;
};
std::vector<N> v[MAX];
struct Q {
int l, r, id;
ll ans = 0;
} q[MAX];
bool cmp(Q a, Q b) {
if (a.l / _sz != b.l / _sz) {
return a.l < b.l;
}
return ((a.l / _sz) & 1) ? a.r < b.r : a.r > b.r;
}
void init() {
sz = sqrt(MX);
for (int i = 1; i <= sz; ++i) {
st[i] = (MX / sz) * (i - 1) + 1;
ed[i] = (MX / sz) * i;
}
ed[sz] = MX;
for (int i = 1; i <= sz; ++i) {
for (int j = st[i]; j <= ed[i]; ++j) {
bl[j] = i;
}
}
}
void clear() {
memset(b1, 0, sizeof(b1));
memset(b2, 0, sizeof(b2));
}
void mdf(int x) {
for (int i = bl[x]; i <= sz; ++i) {
++b1[i];
}
for (int i = x; i <= ed[bl[x]]; ++i) {
++b2[i];
}
}
int qry(int x) {
if (!x) {
return 0;
}
return b1[bl[x] - 1] + b2[x];
}
int main() {
scanf("%d%d", &n, &m);
init();
_sz = sqrt(m);
for (int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
b[i] = a[i];
}
std::sort(b + 1, b + n + 1);
int len = std::unique(b + 1, b + n + 1) - b - 1;
for (int i = 1; i <= n; ++i) {
a[i] = std::lower_bound(b + 1, b + len + 1, a[i]) - b;
}
for (int i = 1; i <= n; ++i) {
p1[i] = qry(a[i] - 1);
p2[i] = qry(n) - qry(a[i]);
mdf(a[i]);
}
for (int i = 1; i <= m; ++i) {
scanf("%d%d", &q[i].l, &q[i].r);
q[i].id = i;
}
std::sort(q + 1, q + m + 1, cmp);
clear();
for (int i = 1, L = 1, R = 0; i <= m; ++i) {
int l = q[i].l, r = q[i].r;
if (L > l) {
v[R].push_back({l, L - 1, i, 1, 0});
}
while (L > l) {
--L;
q[i].ans -= p1[L];
}
if (R < r) {
v[L - 1].push_back({R + 1, r, i, -1, 1});
}
while (R < r) {
++R;
q[i].ans += p2[R];
}
if (L < l) {
v[R].push_back({L, l - 1, i, -1, 0});
}
while (L < l) {
q[i].ans += p1[L];
++L;
}
if (R > r) {
v[L - 1].push_back({r + 1, R, i, 1, 1});
}
while (R > r) {
q[i].ans -= p2[R];
--R;
}
}
for (int i = 1; i <= n; ++i) {
mdf(a[i]);
for (N j : v[i]) {
for (int k = j.l; k <= j.r; ++k) {
if (j.y == 0) {
q[j.id].ans += j.x * qry(a[k] - 1);
} else {
q[j.id].ans += j.x * (i - qry(a[k]));
}
}
}
}
for (int i = 2; i <= m; ++i) {
q[i].ans += q[i - 1].ans;
}
for (int i = 1; i <= m; ++i) {
ans[q[i].id] = q[i].ans;
}
for (int i = 1; i <= m; ++i) {
printf("%lld\n", ans[i]);
}
}
|
Luogu P5501 [LnOI2019] 来者不拒,去者不追
多次询问区间中
中所有数的「Abbi 值」之和。
Abbi 值定义为:若
在询问区间
中是第
小,那么它的「Abbi 值」等于
。
我们不妨令
是
中比
大的数之和,
是
中比
大的数的数量,那么我们向右移动右端点时,产生的贡献为
,其它几个方向可同理写出,在此不加赘述。
上式中的
和
依旧可以进行预处理,其余的离线到另一端点上,进行扫描线处理。不难发现我们要处理的依然是
次插入、
次询问排名的问题,因此同样使用值域分块解决。
示例代码
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 | #include <algorithm>
#include <cmath>
#include <cstring>
#include <iostream>
#include <vector>
constexpr int MAX = 1e5 + 5;
constexpr int MAXX = 4e2 + 5;
constexpr int MX = 1e5;
typedef long long ll;
ll n, m, sz, _sz, sm;
ll st[MAXX], ed[MAXX], bl[MAX], a[MAX], ans[MAX], sm1[MAXX], sm3[MAXX],
sm2[MAX], sm4[MAX], s[MAX], pre[MAX];
void init() {
sz = sqrt(MX);
for (int i = 1; i <= sz; ++i) {
st[i] = (MX / sz) * (i - 1) + 1;
ed[i] = (MX / sz) * i;
}
ed[sz] = MX;
for (int i = 1; i <= sz; ++i) {
for (int j = st[i]; j <= ed[i]; ++j) {
bl[j] = i;
}
}
}
void mdf(ll x) {
sm += x;
for (int i = bl[x]; i <= sz; ++i) {
++sm1[i];
sm3[i] += x;
}
for (int i = x; i <= ed[bl[x]]; ++i) {
++sm2[i];
sm4[i] += x;
}
}
ll qry(ll x) {
if (!x) {
return 0;
}
return sm1[bl[x] - 1] + sm2[x];
}
ll _qry(ll x) {
if (!x) {
return 0;
}
return sm3[bl[x] - 1] + sm4[x];
}
void clear() {
sm = 0;
memset(sm1, 0, sizeof(sm1));
memset(sm2, 0, sizeof(sm2));
memset(sm3, 0, sizeof(sm3));
memset(sm4, 0, sizeof(sm4));
}
struct Q {
ll l, r, id, ans;
} q[MAX];
struct N {
ll l, r, id, x;
};
std::vector<N> v[MAX];
bool cmp(Q a, Q b) {
if (a.l / _sz != b.l / _sz) {
return a.l < b.l;
} else {
return ((a.l / _sz) & 1) ? a.r < b.r : a.r > b.r;
}
}
int main() {
scanf("%lld%lld", &n, &m);
_sz = n / sqrt(m);
init();
for (int i = 1; i <= n; ++i) {
scanf("%lld", &a[i]);
s[i] = s[i - 1] + a[i];
}
for (int i = 1; i <= n; ++i) {
pre[i] = qry(a[i] - 1) * a[i] + sm - _qry(a[i]);
mdf(a[i]);
}
clear();
for (int i = 1; i <= m; ++i) {
scanf("%lld%lld", &q[i].l, &q[i].r);
q[i].id = i;
}
std::sort(q + 1, q + m + 1, cmp);
for (int i = 1, L = 1, R = 0; i <= m; ++i) {
int l = q[i].l, r = q[i].r;
if (L > l) {
v[R].push_back({l, L - 1, i, 1});
}
while (L > l) {
--L;
q[i].ans -= pre[L];
}
if (R < r) {
v[L - 1].push_back({R + 1, r, i, -1});
}
while (R < r) {
++R;
q[i].ans += pre[R];
}
if (L < l) {
v[R].push_back({L, l - 1, i, -1});
}
while (L < l) {
q[i].ans += pre[L];
++L;
}
if (R > r) {
v[L - 1].push_back({r + 1, R, i, 1});
}
while (R > r) {
q[i].ans -= pre[R];
--R;
}
}
for (int i = 1; i <= n; ++i) {
mdf(a[i]);
for (N j : v[i]) {
for (int k = j.l; k <= j.r; ++k) {
q[j.id].ans += 1ll * j.x * (qry(a[k] - 1) * a[k] + sm - _qry(a[k]));
}
}
}
for (int i = 2; i <= m; ++i) {
q[i].ans += q[i - 1].ans;
}
for (int i = 1; i <= m; ++i) {
ans[q[i].id] = q[i].ans + s[q[i].r] - s[q[i].l - 1];
}
for (int i = 1; i <= m; ++i) {
printf("%lld\n", ans[i]);
}
return 0;
}
|
本页面最近更新:2025/3/18 16:08:43,更新历史
发现错误?想一起完善? 在 GitHub 上编辑此页!
本页面贡献者:AtomAlpaca, HeRaNO, Lyccrius, lyccrius, megakite, Tiphereth-A
本页面的全部内容在 CC BY-SA 4.0 和 SATA 协议之条款下提供,附加条款亦可能应用