分块大法好!
概述
分块,就是把数列分成若干个块,分别维护每个块内的信息,查询只需调用每个块内的信息,是一种非常灵活的数据结构
首先定义变量:
l
e
n
len
len 每个块的大小
n
u
m
num
num 块的数量
b
l
[
i
]
bl[i]
bl[i] 第
i
i
i 号元素属于几号块
l
[
i
]
,
r
[
i
]
l[i],r[i]
l[i],r[i] 每个块的左右端点
根据均值不等式,我们建 n \sqrt n n 个块最优,每个块大小也是 n \sqrt n n
怎么能够建块?
void build() {
len = sqrt(n), num = (n - 1) / len + 1 ;
rep(i, 1, n) bl[i] = (i - 1) / len + 1 ;
rep(i, 1, num) l[i] = (i - 1) * len + 1, r[i] = i * len ;
r[num] = n ; // 注意
}
代码还是比较显然,唯一需要注意的是最后一个块会比之前的小,因为他的右端点必定是 n n n
之后我们通过 L O J LOJ LOJ 的 9 9 9 个 题目进行分析 P r o b l e m S e t Problem \ Set Problem Set
例题1
给出一个长为 n n n 的数列,以及 n n n 个操作,操作涉及区间加法,单点查值。
先从最基础的题目开始
我们用 t a g [ i ] tag[i] tag[i] 记录第 i i i 个块增加的值
对于一个修改操作 [ x , y ] [x,y] [x,y]
如果 x x x 和 y y y 属于同一块,直接暴力修改 a [ x . . . y ] a[x...y] a[x...y]
否则,对于整块 直接修改 t a g tag tag, 而其他的零散元素直接暴力修改 a [ i ] a[i] a[i]
对于查询的 x x x,答案就是 a [ x ] + t a g [ b l [ x ] ] a[x]+tag[bl[x]] a[x]+tag[bl[x]]
时间复杂度 O ( n n ) O(n~\sqrt n) O(n n)
const int N = 100010 ;
const int M = 320 ;
int bl[N], l[M], r[M], tag[M], a[N] ;
int n, len, num ;
/* bl[i] : i号元素属于的块的编号
* l[i] 块i的左端点
r[i] 块i的右端点
tag[i] i号块元素都加的值
*/
void build() {
len = sqrt(n), num = (n - 1) / len + 1 ;
rep(i, 1, n) bl[i] = (i - 1) / len + 1 ;
rep(i, 1, num) l[i] = (i - 1) * len + 1, r[i] = i * len ;
r[num] = n ;
}
void change(int x, int y, int val) {
if (bl[x] == bl[y]) rep(i, x, y) a[i] += val ; // 同一个块,直接修改
else {
rep(i, bl[x] + 1, bl[y] - 1) tag[i] += val ; // 大块处理
rep(i, x, r[bl[x]]) a[i] += val ; // 边角暴力
rep(i, l[bl[y]], y) a[i] += val ;
}
}
int query(int x) {
return a[x] + tag[bl[x]] ; // O(1)查询
}
signed main(){
scanf("%d", &n) ; int m = n ;
rep(i, 1, n) scanf("%d", &a[i]) ;
build() ;
while (m--) {
int op ; scanf("%d", &op) ;
if (op == 0) {
int x, y, val ; scanf("%d%d%d", &x, &y, &val) ;
change(x, y, val) ;
} else {
int x, y, val ; scanf("%d%d%d", &x, &y, &val) ;
printf("%d\n", query(y)) ;
}
}
return 0 ;
}
通过这一题的练习,我们能够大概概括分块的工作原理:大块处理,小块暴力
例题 2
给出一个长为 n n n 的数列,以及 n n n 个操作,操作涉及区间加法,询问区间内小于某个值 x x x 的元素个数。
首先你应该知道没有修改操作怎么做:
- 对于零散操作,直接暴力查找
- 对于整块,排序后二分查找
带上修改操作依然也很简单
- 对于整块,我们发现相对顺序不会改变,所以不需要重新排序,只需要改变二分的数为 v − t a g [ i ] v-tag[i] v−tag[i] 就行了, 时间复杂度 O ( n ) O(\sqrt n) O(n)
- 对于零散元素,没有办法,只能暴力修改在重新排序,时间复杂度 O ( n l o g n ) O(\sqrt n \ log \ n) O(n log n)
分析总时间复杂度为 O ( n n l o g n ) O(n \sqrt n \ log \ n) O(nn log n)
int n, m, len, num ;
int a[N], bl[N], l[M], r[M], tag[M] ;
vi v[M] ;
void build() {
len = sqrt(n), num = (n - 1) / len + 1 ;
rep(i, 1, n) {
bl[i] = (i - 1) / len + 1 ;
v[bl[i]].pb(a[i]) ;
}
rep(i, 1, num) {
l[i] = (i - 1) * len + 1 ;
r[i] = i * len ;
sort(v[i].begin(), v[i].end()) ;
}
r[num] = n ;
}
void reset(int x) { // 对块x更新
v[x].clear() ;
rep(i, l[x], r[x]) v[x].pb(a[i]) ;
sort(v[x].begin(), v[x].end()) ;
}
void change(int x, int y, int val) {
if (bl[x] == bl[y]) { // 同一块中,暴力
rep(i, x, y) a[i] += val ;
reset(bl[x]) ;
} else {
rep(i, bl[x] + 1, bl[y] - 1) tag[i] += val ;
rep(i, x, r[bl[x]]) a[i] += val ;
rep(i, l[bl[y]], y) a[i] += val ;
reset(bl[x]) ; reset(bl[y]) ; // 更新边角
}
}
int query(int x, int y, int val) {
int ans = 0 ;
if (bl[x] == bl[y]) { // 同一块中,暴力查找
rep(i, x, y) if (a[i] + tag[bl[i]] < val) ans++ ;
} else {
rep(i, bl[x] + 1, bl[y] - 1)
ans += lower_bound(v[i].begin(), v[i].end(), val - tag[i]) - v[i].begin() ; // 大块二分
rep(i, x, r[bl[x]]) if (a[i] + tag[bl[i]] < val) ans++ ; // 小块暴力
rep(i, l[bl[y]], y) if (a[i] + tag[bl[i]] < val) ans++ ;
}
return ans ;
}
signed main(){
scanf("%d", &n) ; int m = n ;
rep(i, 1, n) scanf("%d", &a[i]) ;
build() ;
while (m--) {
int op, x, y, val ;
scanf("%d%d%d%d", &op, &x, &y, &val) ;
if (!op) change(x, y, val) ;
else printf("%d\n", query(x, y, val * val)) ;
}
return 0 ;
}
例题 3
给定一个长度为 n n n 的序列和 n n n 个操作。支持区间加法,区间查询 v v v 的前驱。
这个跟前面那个差不多,只需要修改一下二分就行,读者可以直接看代码
int n, m, len, num ;
int a[N], bl[N], l[M], r[M], tag[M] ;
vi v[M] ;
void build() {
len = sqrt(n), num = (n - 1) / len + 1 ;
rep(i, 1, n) {
bl[i] = (i - 1) / len + 1 ;
v[bl[i]].pb(a[i]) ;
}
rep(i, 1, num) {
l[i] = (i - 1) * len - 1, r[i] = i * len ;
sort(v[i].begin(), v[i].end()) ;
}
r[num] = n ;
}
void reset(int x) {
v[x].clear() ;
rep(i, l[x], r[x]) v[x].pb(a[i]) ;
sort(v[x].begin(), v[x].end()) ;
}
void change(int x, int y, int val) {
if (bl[x] == bl[y]) {
rep(i, x, y) a[i] += val ;
reset(bl[x]) ;
} else {
rep(i, bl[x] + 1, bl[y] - 1) tag[i] += val ;
rep(i, x, r[bl[x]]) a[i] += val ;
rep(i, l[bl[y]], y) a[i] += val ;
reset(bl[x]) ; reset(bl[y]) ;
}
}
int query(int x, int y, int val) {
int ans = -1 ;
if (bl[x] == bl[y]) {
rep(i, x, y) if (a[i] + tag[bl[i]] < val) ans = max(ans, a[i] + tag[bl[i]]) ;
} else {
rep(i, bl[x] + 1, bl[y] - 1) {
vector <int>::iterator it = lower_bound(v[i].begin(), v[i].end(), val - tag[i]) ;
if (it != v[i].begin()) ans = max(ans, *(--it) + tag[i]) ;
}
rep(i, x, r[bl[x]]) if (a[i] + tag[bl[i]] < val) ans = max(ans, a[i] + tag[bl[i]]) ;
rep(i, l[bl[y]], y) if (a[i] + tag[bl[i]] < val) ans = max(ans, a[i] + tag[bl[i]]) ;
}
return ans ;
}
signed main(){
scanf("%d", &n) ; int m = n ;
rep(i, 1, n) scanf("%d", &a[i]) ;
build() ;
while (m--) {
int op, x, y, val ;
scanf("%d%d%d%d", &op, &x, &y, &val) ;
if (!op) change(x, y, val) ;
else printf("%d\n", query(x, y, val)) ;
}
return 0 ;
}
例题 4
给定一个长度为 n n n 的序列和 n n n 个操作。支持区间加法,区间求和。答案对 1 0 9 + 7 10^9+7 109+7 取模。
区间求和? 光用 t a g tag tag 查询就变成 O ( n ) O(\sqrt n) O(n) 的了
我们定义 s u m [ i ] sum[i] sum[i] 表示第 i i i 个块不算 t a g [ i ] tag[i] tag[i] 的总和
我们考虑修改操作
- 对于零散元素,直接暴力修改 a [ i ] a[i] a[i] 和 t a g [ b l [ i ] ] tag[bl[i]] tag[bl[i]]
- 对于整块,只要修改
t
a
g
[
i
]
tag[i]
tag[i]
对于查询操作: - 对于零散元素,我们用 a [ i ] + t a g [ b l [ i ] ] a[i]+tag[bl[i]] a[i]+tag[bl[i]] 更新答案
- 整块,答案就是 s u m [ i ] + t a g [ i ] ∗ l e n sum[i]+tag[i]*len sum[i]+tag[i]∗len
时间复杂度 O ( n n ) O(n \sqrt n) O(nn)
int n, m, len, num ;
ll a[N], bl[N], l[M], r[M], tag[M], sum[M] ;
void build() {
len = sqrt(n) ; num = (n - 1) / len + 1 ;
rep(i, 1, n) {
bl[i] = (i - 1) / len + 1 ;
sum[bl[i]] += a[i] ;
}
rep(i, 1, num) {
l[i] = (i - 1) * len + 1 ;
r[i] = i * len ;
}
r[num] = n ;
}
void change(int x, int y, int val) {
if (bl[x] == bl[y]) {
rep(i, x, y) a[i] += val ;
sum[bl[x]] += 1ll * (y - x + 1) * val ;
} else {
rep(i, bl[x] + 1, bl[y] - 1) tag[i] += val ;
rep(i, x, r[bl[x]]) a[i] += val ;
sum[bl[x]] += 1ll * (r[bl[x]] - x + 1) * val ;
rep(i, l[bl[y]], y) a[i] += val ;
sum[bl[y]] += 1ll * (y - l[bl[y]] + 1) * val ;
}
}
ll query(int x, int y, int MOD) {
int ans = 0 ;
if (bl[x] == bl[y]) {
rep(i, x, y) ans = (ans + a[i]) % MOD ;
ans = (ans + 1ll * (y - x + 1) * tag[bl[x]] % MOD) % MOD ;
} else {
rep(i, bl[x] + 1, bl[y] - 1) {
ans = (ans + sum[i]) % MOD ;
ans = (ans + 1ll * tag[i] * len % MOD) % MOD ;
}
rep(i, x, r[bl[x]]) ans = (ans + a[i]) % MOD ;
ans = (ans + 1ll * (r[bl[x]] - x + 1) * tag[bl[x]] % MOD) % MOD ;
rep(i, l[bl[y]], y) ans = (ans + a[i]) % MOD ;
ans = (ans + 1ll * (y - l[bl[y]] + 1) * tag[bl[y]] % MOD) % MOD ;
}
return ans ;
}
signed main(){
scanf("%d", &n) ; m = n ;
rep(i, 1, n) scanf("%lld", &a[i]) ;
build() ;
while (m--) {
int op, l, r, val ; scanf("%d%d%d%d", &op, &l, &r, &val) ;
if (!op) change(l, r, val) ;
else printf("%lld\n", query(l, r, val + 1)) ;
}
return 0 ;
}
例题5
给定一个长度为 n n n 的序列和 n n n 个操作。支持区间开方,区间求和。
这题也很套路,做过 “花神游历各国” 的人应该都知道这个套路
我们发现一个 1 0 9 10^9 109 的数,最多开 5 5 5次平方就会变成 1 1 1
显然,对于一个全部都是1的区间做开放操作显然没有意义
那么我们就可以记录那些块还可以进行开放操作,暴力搞就完事了
时间复杂度 O ( n n ) O(n \sqrt n) O(nn)
int n, m, len, num ;
int a[N], bl[N], l[M], r[M], flg[M], sum[M] ;
void build() {
len = sqrt(n) ; num = (n - 1) / len + 1 ;
rep(i, 1, n) bl[i] = (i - 1) / len + 1, sum[bl[i]] += a[i] ;
rep(i, 1, num) l[i] = (i - 1) * len + 1, r[i] = i * len ;
r[num] = n ;
}
void modify(int x, int y) {
if (bl[x] == bl[y]) {
rep(i, x, y) {
sum[bl[i]] -= a[i] ;
a[i] = sqrt(a[i]) ;
sum[bl[i]] += a[i] ;
}
} else {
rep(i, bl[x] + 1, bl[y] - 1) {
if (flg[i]) continue ;
sum[i] = 0, flg[i] = 1 ;
rep(j, l[i], r[i]) {
a[j] = sqrt(a[j]) ;
sum[i] += a[j] ;
flg[i] &= (a[j] <= 1) ;
}
}
rep(i, x, r[bl[x]]) {
sum[bl[i]] -= a[i] ;
a[i] = sqrt(a[i]) ;
sum[bl[i]] += a[i] ;
}
rep(i, l[bl[y]], y) {
sum[bl[i]] -= a[i] ;
a[i] = sqrt(a[i]) ;
sum[bl[i]] += a[i] ;
}
}
}
int query(int x, int y) {
int ans = 0 ;
if (bl[x] == bl[y]) {
rep(i, x, y) ans += a[i] ;
} else {
rep(i, bl[x] + 1, bl[y] - 1) ans += sum[i] ;
rep(i, x, r[bl[x]]) ans += a[i] ;
rep(i, l[bl[y]], y) ans += a[i] ;
}
return ans ;
}
signed main(){
scanf("%d", &n) ; m = n ;
rep(i, 1, n) scanf("%d", &a[i]) ;
build() ;
while (m--) {
int op, x, y, val ;
scanf("%d%d%d%d", &op, &x, &y, &val) ;
if (op == 0) modify(x, y) ;
else printf("%d\n", query(x, y)) ;
}
return 0 ;
}
例题6
给出一个长为 n n n 的数列,以及 n n n 个操作,操作涉及单点插入,单点询问,数据随机生成。
下面算法能够应对非随机生成
对于随机生成的数据,每个块内的插入次数是均摊的,我们只需要通过一个 v e c t o r vector vector 就能够搞定
但是在极限数据下,块内插入可能多达 n n n 次,可能会使得一个块特别大的情况
那么我们为了维持时间复杂度,可以对块进行重构,当块大小达到 20 ∗ n 20*\sqrt n 20∗n 时,对序列重构
总时间复杂度 O ( n n ) O(n \sqrt n) O(nn)
int n, m, len, num ;
int a[N], st[N << 1], bl[M] ;
vi v[M] ;
void build() {
len = sqrt(n), num = (n - 1) / len + 1 ;
rep(i, 1, n) bl[i] = (i - 1) / len + 1, v[bl[i]].pb(a[i]) ;
}
void cg() { // 重构
int top = 0 ;
rep(i, 1, num) {
rep(j, 0, siz(v[i]) - 1) st[++top] = v[i][j] ;
v[i].clear() ;
}
len = sqrt(top), num = (top - 1) / len + 1 ;
rep(i, 1, top) bl[i] = (i - 1) / len + 1, v[bl[i]].pb(st[i]) ;
}
pii get(int k) { // 找到第x号元素所在的块以及位置
int x = 1 ;
while (k > siz(v[x])) k -= siz(v[x]), x++ ;
return mp(x, k - 1) ;
}
void change(int x, int y) {
pii r = get(x) ;
int i = r.fi ;
v[i].insert(v[i].begin() + r.se, y) ; // 直接用vector维护
if (siz(v[i]) > 20 * len) cg() ;
}
int query(int x) {
pii r = get(x) ;
return v[r.fi][r.se] ;
}
signed main(){
scanf("%d", &n) ; m = n ;
rep(i, 1, n) scanf("%d", &a[i]) ;
build() ;
while (m--) {
int op, x, y, val ; scanf("%d%d%d%d", &op, &x, &y, &val) ;
if (!op) change(x, y) ;
else printf("%d\n", query(y)) ;
}
return 0 ;
}
例题 7 7 7
给出一个长为 n n n 的数列,以及 n n n 个操作,操作涉及区间乘法,区间加法,单点询问。
又是一个很套路的题
用线段树怎么做这个题的? 开两个数组 m u l [ i ] , a d d [ i ] mul[i], add[i] mul[i],add[i] 然后维护
那么分块也是类似
先强制乘法比加法优先
对每个块,用两个标记 a d d [ i ] , m u l [ i ] add[i], mul[i] add[i],mul[i] 分别表示整个块内加和乘的值
对于一个元素 a [ i ] a[i] a[i] 实际的值为 a [ i ] ∗ m u l [ b l [ i ] ] + a d d [ b l [ i ] ] a[i]*mul[bl[i]]+add[bl[i]] a[i]∗mul[bl[i]]+add[bl[i]]
对于乘法修改操作,标记 a d d [ i ] add[i] add[i] 和 m u l [ i ] mul[i] mul[i] 分别变成 a d d [ i ] ∗ v a l add[i]*val add[i]∗val 和 m u l [ i ] ∗ v a l mul[i]*val mul[i]∗val
对于加法操作, 标记 a d d [ i ] add[i] add[i] 和 m u l [ i ] mul[i] mul[i] 分别变成 a d d [ i ] + v a l add[i]+val add[i]+val 和 m u l [ i ] mul[i] mul[i]
对于零散元素,直接暴力战役,标记清空,然后对其本身修改即可
int n, m, len, num ;
int a[N], bl[N], l[M], r[M], add[M], mul[M] ;
void build() {
len = sqrt(n), num = (n - 1) / len + 1 ;
rep(i, 1, n) bl[i] = (i - 1) / len + 1 ;
rep(i, 1, num) l[i] = (i - 1) * len + 1, r[i] = i * len, mul[i] = 1 ;
r[num] = n ;
}
void reset(int x) {
rep(i, l[x], r[x]) a[i] = (a[i] * mul[x] + add[x]) % MOD ;
add[x] = 0, mul[x] = 1 ;
}
void Add(int x, int y, int val) {
if (bl[x] == bl[y]) {
reset(bl[x]) ;
rep(i, x, y) a[i] = (a[i] + val) % MOD ;
} else {
rep(i, bl[x] + 1, bl[y] - 1) add[i] = (add[i] + val) % MOD ;
reset(bl[x]) ; reset(bl[y]) ;
rep(i, x, r[bl[x]]) a[i] = (a[i] + val) % MOD ;
rep(i, l[bl[y]], y) a[i] = (a[i] + val) % MOD ;
}
}
void Mul(int x, int y, int val) {
if (bl[x] == bl[y]) {
reset(bl[x]) ;
rep(i, x, y) a[i] = a[i] * val % MOD ;
} else {
rep(i, bl[x] + 1, bl[y] - 1) {
add[i] = add[i] * val % MOD ;
mul[i] = mul[i] * val % MOD ;
}
reset(bl[x]) ; reset(bl[y]) ;
rep(i, x, r[bl[x]]) a[i] = a[i] * val % MOD ;
rep(i, l[bl[y]], y) a[i] = a[i] * val % MOD ;
}
}
int query(int x) {
return (a[x] * mul[bl[x]] + add[bl[x]]) % MOD ;
}
signed main(){
scanf("%d", &n) ; m = n ;
rep(i, 1, n) scanf("%d", &a[i]) ;
build() ;
while (m--) {
int op, l, r, val ; scanf("%d%d%d%d", &op, &l, &r, &val) ;
if (op == 0) Add(l, r, val % MOD) ;
else if (op == 1) Mul(l, r, val % MOD) ;
else printf("%d\n", query(r)) ;
}
return 0 ;
}
例题 8 8 8
给出一个长为 n n n 的数列,以及 n n n 个操作,操作涉及区间询问等于一个数 c c c 的元素,并将这个区间的所有元素改为 c c c。
我们考虑一个暴力做法
对于修改操作
- 对于零散元素,我们暴力修改
- 对于整块,对块打上标记,均为 v a l val val
对于询问操作
- 对于零散元素,我们下放标记暴力查询
- 对于整块,如果有标记为 v a l val val, 则对答案有 l e n len len 的贡献,否则暴力查询
时间复杂度 O ( n 2 ) O(n^2) O(n2) ?
仔细分析:
假设初始序列都是同一个值,那么单词查询的时间复杂度为 O ( n ) O(\sqrt n) O(n)
如果是区间操作, 那么最多破坏环首位为 2 2 2 的块的标记,使得我们增加两个块的暴力时间,均摊时间复杂依然是 O ( n ) O(\sqrt n) O(n)
简单地说,出题人想要你耗费一次 O ( n ) O(n) O(n) 的时间回答,他要花费 O ( n ) O(\sqrt n) O(n) 个修改操作
所以,均摊时间复杂度 O ( n n ) O(n \sqrt n) O(nn)
int n, m, len, num ;
int a[N], bl[N], l[M], r[M], cov[M], tag[M] ;
void build() {
len = sqrt(n), num = (n - 1) / len + 1 ;
rep(i, 1, n) bl[i] = (i - 1) / len + 1 ;
rep(i, 1, num) l[i] = (i - 1) * len + 1, r[i] = i * len ;
r[num] = n ;
}
void reset(int x) {
if (!tag[x]) return ;
rep(i, l[x], r[x]) a[i] = cov[x] ;
tag[x] = 0 ;
}
void modify(int x, int y, int val) {
if (bl[x] == bl[y]) {
reset(bl[x]) ;
rep(i, x, y) a[i] = val ;
} else {
rep(i, bl[x] + 1, bl[y] - 1) tag[i] = 1, cov[i] = val ;
reset(bl[x]) ; reset(bl[y]) ;
rep(i, x, r[bl[x]]) a[i] = val ;
rep(i, l[bl[y]], y) a[i] = val ;
}
}
int query(int x, int y, int val) {
int ans = 0 ;
if (bl[x] == bl[y]) {
reset(bl[x]) ;
rep(i, x, y) ans += (a[i] == val) ;
} else {
rep(i, bl[x] + 1, bl[y] - 1)
if (tag[i]) ans += (cov[i] == val ? len : 0) ;
else {
rep(j, l[i], r[i]) ans += (a[j] == val) ;
}
reset(bl[x]) ; reset(bl[y]) ;
rep(i, x, r[bl[x]]) ans += (a[i] == val) ;
rep(i, l[bl[y]], y) ans += (a[i] == val) ;
}
return ans ;
}
signed main(){
scanf("%d", &n) ; m = n ;
rep(i, 1, n) scanf("%d", &a[i]) ;
build() ;
while (m--) {
int x, y, val ; scanf("%d%d%d", &x, &y, &val) ;
printf("%d\n", query(x, y, val)) ;
modify(x, y, val) ;
}
return 0 ;
}
例题 9 9 9
给出一个长为 n n n 的数列,以及 n n n 个操作,操作涉及询问区间的最小众数。
这个就稍有难度了,我们先考虑没有修改操作的问题
首先定义 m o d e ( i ) mode(i) mode(i) 为第 i i i 个整块的众数
我们发现一个性质:对于一个询问 [ x , y ] [x,y] [x,y], 答案 ∈ m o d e ( i \in mode(i ∈mode(i~ j ) ∪ j)~ \cup j) ∪ 零散元素
这个性质显然
令 x x x 属于块 a a a, y y y 属于块 b b b
那么 [ x , y ] [x,y] [x,y] 可以拆分成:
- 从 x x x 到块 a a a 的尾元素
- 从块 a + 1 a+1 a+1 到块 b − 1 b-1 b−1
- 从块 b b b 的首元素到 y y y
根据这个性质,我们只需要比较至多 n \sqrt n n 个数的出现次数即可
我们可以 O ( n n ) O(n \sqrt n) O(nn) 预处理 f [ i ] [ j ] f[i][j] f[i][j] 表示第 i i i 个整块到第 j j j 个整块的众数,每个数开一个 v e c t o r vector vector 记录 i i i 的位置
对于询问 [ x , y ] [x,y] [x,y]
- 对于零散元素,我们暴力查找每个元素在 [ x , y ] [x,y] [x,y] 内的出现次数,这个过程可以通过记录 a [ i ] a[i] a[i] 的出现位置的 v [ a [ i ] ] v[a[i]] v[a[i]] 进行二分查找
- 对于整块,众数为 f [ b l [ x ] + 1 ] [ b l [ y ] − 1 ] f[bl[x]+1][bl[y]-1] f[bl[x]+1][bl[y]−1]
我们发现只需要在这 O ( n ) O(\sqrt n) O(n) 个数内找到出现次数最多的数即可
注意,为了方便计算我们要对数据离散化
时间复杂度 O ( n n l o g n ) O(n \sqrt n ~log~n) O(nn log n)
int n, m, len, num ;
int a[N], tmp[N], bl[N], l[M], r[M], cnt[N], f[M][M] ;
vi p[N] ;
void build() {
rep(i, 1, n) tmp[i] = a[i] ;
sort(tmp + 1, tmp + n + 1) ;
int sz = unique(tmp + 1, tmp + n + 1) - tmp - 1 ;
rep(i, 1, n) {
a[i] = lower_bound(tmp + 1, tmp + sz + 1, a[i]) - tmp ;
p[a[i]].pb(i) ;
}
len = sqrt(n / log2(n)), num = (n - 1) / len + 1 ;
rep(i, 1, n) bl[i] = (i - 1) / len + 1 ;
rep(i, 1, num) l[i] = (i - 1) * len + 1, r[i] = i * len ;
r[num] = n ;
}
void init() {
rep(i, 1, num) {
clr(cnt) ;
int ans = 0, mx = 0 ;
rep(j, i, num) {
rep(k, l[j], r[j]) {
cnt[a[k]]++ ;
if (cnt[a[k]] > mx || (cnt[a[k]] == mx && a[k] < ans)) ans = a[k], mx = cnt[a[k]] ;
}
f[i][j] = ans ;
}
}
}
int sum(int l, int r, int val) {
return upper_bound(p[val].begin(), p[val].end(), r) -
lower_bound(p[val].begin(), p[val].end(), l) ;
}
void solve(int x, int y, int l, int r, int &ans, int &mx) {
rep(i, x, y) {
int now = sum(l, r, a[i]) ;
if (now > mx || (now == mx && a[i] < ans)) ans = a[i], mx = now ;
}
}
int query(int x, int y) {
int ans = 0, mx = 0 ;
if (bl[x] + 1 >= bl[y]) solve(x, y, x, y, ans, mx) ;
else {
ans = f[bl[x] + 1][bl[y] - 1], mx = sum(x, y, ans) ;
solve(x, r[bl[x]], x, y, ans, mx) ;
solve(l[bl[y]], y, x, y, ans, mx) ;
}
return ans ;
}
signed main(){
scanf("%d", &n) ; m = n ;
rep(i, 1, n) scanf("%d", &a[i]) ;
build() ; init() ;
while (m--) {
int x, y ; scanf("%d%d", &x, &y) ;
printf("%d\n", tmp[query(x, y)]) ;
}
return 0 ;
}
谢谢阅读