数列分块入门 1~9 题解

分块大法好!

概述

分块,就是把数列分成若干个块,分别维护每个块内的信息,查询只需调用每个块内的信息,是一种非常灵活的数据结构

首先定义变量:

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] vtag[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 20n 时,对序列重构

总时间复杂度 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 b1
  • 从块 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 ;
}

谢谢阅读

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值