分块 学习笔记

众所周知,分块是一种优雅的暴力,一般来说复杂度带根号。由于本人对分块理解的还不是很深,如何算块取多长是最优的暂且不会,这篇文章中的块长都设为 n \sqrt n n

具体操作也很简单,先将原序列分成 n \sqrt n n 个块,大块 ( ( (即整块 ) ) )打标记 ( ( (类似于线段树 ) ) ),小块暴力修改。由于给出的一段区间,最多会包含 2 2 2 个边上的小块,最多会包含 n \sqrt n n 个大块。小块的操作是 O ( n ) O(\sqrt n) O(n ),大块打标记是 O ( 1 ) O(1) O(1),这么算下来大块和小块的复杂度都是 O ( n ) O(\sqrt n) O(n ),所以可以说复杂度就是 O ( n ) O(\sqrt n) O(n )。当然在块中,也可以加入数据结构。

数列分块入门 1 1 1

给出一个长度为 n n n 的数列,以及 n n n 个操作,操作涉及区间加法,单点查值。

这就是最基础的分块,区间加法像上文所说,大块打标记,小块暴力修改。最后查答案的时候别忘了把标记给加上。

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#define re register
#define drep(a,b,c) for(re int a(b) ; a>=(c) ; --a)
#define rep(a,b,c) 	for(re int a(b) ; a<=(c) ; ++a)
using namespace std;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch == '-') f=-1 ; ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
inline void print(int x){
	if(x < 0) putchar('-'),x = -x;
	if(x >= 10) print(x / 10);
	putchar(x % 10 + '0');
}
const int M = 5e4+10;
int n,B;
int a[M],tag[M];
inline void modify(int l,int r,int w){
	int idl = l/B,idr = r/B;
	if(idl == idr) rep(i,l,r) a[i] += w;
	else{
		rep(i,l,(idl+1)*B-1) a[i] += w;
		rep(id,idl+1,idr-1) tag[id] += w;
		rep(i,idr*B,r) a[i] += w;
	}
}
signed main(){
	n = read();
	B = sqrt(n);
	rep(i,1,n) a[i] = read();
	while(n--){
		int op = read(),l = read(),r = read(),c = read();
		if(op == 0)	modify(l,r,c);
		else printf("%d\n",a[r]+tag[r/B]);
	}
	return 0;
}

数列分块入门 2 2 2

给出一个长为 n n n 的数列,以及 n n n 个操作,操作涉及区间加法,询问区间内小于某个值 x x x 的元素个数。

看到询问区间内小于某个元素的个数,可以想到二分。具体的操作就是,每一个块维护一个有序的 v e c t o r vector vector,每次查询直接 l o w e r lower lower_ b o u n d bound bound 查答案即可。这里对于大块,同时加上一个数,整体的大小关系不会变,所以直接打上加法标记即可。
对于小块,修改会改变块内相对的大小关系。这是我们暴力将数组加上一个数,然后将这个块维护的 v e c t o r vector vector 重新排序。这样就能保证每次操作的 v e c t o r vector vector 都是有序的。每次最多只会这么重构两个小块,所以复杂度是正确的。

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <vector>
#define re register
#define int long long
#define drep(a,b,c) for(re int a(b) ; a>=(c) ; --a)
#define rep(a,b,c) 	for(re int a(b) ; a<=(c) ; ++a)
using namespace std;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch == '-') f=-1 ; ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
inline void print(int x){
	if(x < 0) putchar('-'),x = -x;
	if(x >= 10) print(x / 10);
	putchar(x % 10 + '0');
}
const int M = 1e5+10;
int n,B;
int a[M],base[M];
vector<int> v[M];
inline void modify(int l,int r,int w){
	int idl = l/B,idr = r/B;
	if(idl == idr){
		rep(i,l,r) a[i] += w;
		v[idl].clear();
		rep(i,max(1ll,idl*B),min((idl+1)*B-1,n)) v[idl].push_back(a[i]);
		sort(v[idl].begin(),v[idl].end());
	}
	else{
		rep(i,l,(idl+1)*B-1) a[i] += w;
		v[idl].clear();
		rep(i,max(1ll,idl*B),(idl+1)*B-1) v[idl].push_back(a[i]);
		sort(v[idl].begin(),v[idl].end());
		
		rep(id,idl+1,idr-1) base[id] += w;
		
		rep(i,idr*B,r) a[i] += w;
		v[idr].clear();
		rep(i,idr*B,min(n,(idr+1)*B-1)) v[idr].push_back(a[i]);
		sort(v[idr].begin(),v[idr].end());
	}
}
inline int query(int l,int r,int x){
	int idl = l/B,idr = r/B;
	int ans = 0;
	if(idl == idr) rep(i,l,r) ans += (a[i] + base[idl] < x);
	else{
		rep(i,l,(idl+1)*B-1) ans += (a[i] + base[idl] < x);
		rep(id,idl+1,idr-1) ans += lower_bound(v[id].begin(),v[id].end(),x-base[id])-v[id].begin();
		rep(i,idr*B,r) ans += (a[i] + base[idr] < x);
	}
	return ans;
}
signed main(){
	n = read();
	B = sqrt(n);
	rep(i,1,n) a[i] = read();
	rep(i,1,n){
		v[i/B].push_back(a[i]);
		if(i%B == B-1 || i == n-1) sort(v[i/B].begin(),v[i/B].end());
	}
	rep(i,1,n){
		int op = read(),l = read(),r = read(),c = read();
		if(op == 0) modify(l,r,c);
		else printf("%lld\n",query(l,r,c*c));
	}
	return 0;
}

数列分块入门 3 3 3

给出一个长为 n n n 的数列,以及 n n n 个操作,操作涉及区间加法,询问区间内小于某个值 x x x 的前驱 ( ( (比其小的最大元素 ) ) )

跟上一道题类似,也是每一块维护一个有序 v e c t o r vector vector,对于区间加法和上一道题操作相同,找前驱的话,对于边上的小块,我们暴力找,对于中间的大块,我们可以用 l o w e r lower lower_ b o u n d bound bound 二分来找。这里我们要找的 x x x 的前驱,是这些块中小于 x x x 的数的最大值。也就是我们每次要将 a n s ans ans 与满足 < x <x <x 的数取 max ⁡ \max max。别忘了这里的 x x x 要减去加法的标记,才是真正的 x x x

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <vector>
#define re register
#define int long long
#define drep(a,b,c) for(re int a(b) ; a>=(c) ; --a)
#define rep(a,b,c) 	for(re int a(b) ; a<=(c) ; ++a)
using namespace std;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch == '-') f=-1 ; ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
inline void print(int x){
	if(x < 0) putchar('-'),x = -x;
	if(x >= 10) print(x / 10);
	putchar(x % 10 + '0');
}
const int M = 1e5+10;
typedef long long ll;
int n,B;
int a[M],base[M];
vector<int> v[M];
inline void modify(int l,int r,int w){
	int idl = l/B,idr = r/B;
	if(idl == idr){
		rep(i,l,r) a[i] += w;
		v[idl].clear();
		rep(i,max(1ll,idl*B),min((idl+1)*B-1,n)) v[idl].push_back(a[i]);
		sort(v[idl].begin(),v[idl].end());
	}
	else{
		rep(i,l,(idl+1)*B-1) a[i] += w;
		v[idl].clear();
		rep(i,max(1ll,idl*B),(idl+1)*B-1) v[idl].push_back(a[i]);
		sort(v[idl].begin(),v[idl].end());
		
		rep(id,idl+1,idr-1) base[id] += w;
		
		rep(i,idr*B,r) a[i] += w;
		v[idr].clear();
		rep(i,idr*B,min(n,(idr+1)*B-1)) v[idr].push_back(a[i]);
		sort(v[idr].begin(),v[idr].end());
	}
}
inline int query(int l,int r,int x){
	int idl = l/B,idr = r/B;
	int ans = -1e18;
	if(idl == idr) {rep(i,l,r) if(a[i] + base[idl] < x) ans = max(ans,a[i] + base[idl]);}
	else{
		rep(i,l,(idl+1)*B-1) if(a[i] + base[idl] < x) ans = max(ans,a[i] + base[idl]);
		rep(id,idl+1,idr-1){
			auto it = lower_bound(v[id].begin(),v[id].end(),x-base[id]);
			if(it != v[id].begin()) ans = max(ans,*(--it) + base[id]);
		}
		rep(i,idr*B,r) if(a[i] + base[idr] < x) ans = max(ans,a[i] + base[idr]);
	}
	if(ans == -1e18) ans = -1;
	return ans;
}
signed main(){
	n = read();
	B = sqrt(n);
	rep(i,1,n) a[i] = read();
	rep(i,1,n){
		v[i/B].push_back(a[i]);
		if(i%B == B-1 || i == n-1) sort(v[i/B].begin(),v[i/B].end());
	}
	rep(i,1,n){
		int op = read(),l = read(),r = read(),c = read();
		if(op == 0) modify(l,r,c);
		else printf("%lld\n",query(l,r,c));
	}
	return 0;
}

数列分块入门 4 4 4

给出一个长为 n n n 的数列,以及 n n n 个操作,操作涉及区间加法,区间求和。

这个就是基本的操作了,大块打标记,小块暴力修改。我们用 r e s res res 数组记录答案,这里要注意,我们的 r e s res res 始终记录的是正确的答案。

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#define re register
#define int long long
#define drep(a,b,c) for(re int a(b) ; a>=(c) ; --a)
#define rep(a,b,c) 	for(re int a(b) ; a<=(c) ; ++a)
using namespace std;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch == '-') f=-1 ; ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
inline void print(int x){
	if(x < 0) putchar('-'),x = -x;
	if(x >= 10) print(x / 10);
	putchar(x % 10 + '0');
}
const int M = 5e4+10;
int n,B;
int a[M],base[M],res[M];
inline void modify(int l,int r,int w){
	int idl = l/B,idr = r/B;
	if(idl == idr) rep(i,l,r) a[i] += w,res[idl] += w;
	else{
		rep(i,l,(idl+1)*B-1) a[i] += w,res[idl] += w;
		rep(id,idl+1,idr-1) base[id] += w,res[id] += B*w;
		rep(i,idr*B,r) a[i] += w,res[idr] += w;
	}
}
inline int query(int l,int r,int p){
	int idl = l/B,idr = r/B;
	int ans = 0;
	if(idl == idr) rep(i,l,r) ans = (ans + a[i] + base[idl])%p;
	else{
		rep(i,l,(idl+1)*B-1) ans = (ans + a[i] + base[idl])%p;
		rep(id,idl+1,idr-1) ans = (ans + res[id])%p;
		rep(i,idr*B,r) ans = (ans + a[i] + base[idr])%p;
	}
	return ans%p;
}
signed main(){
	n = read();
	B = sqrt(n);
	rep(i,1,n) a[i] = read();
	rep(i,1,n) res[i/B] += a[i];
	while(n--){
		int op = read(),l = read(),r = read(),c = read();
		if(op == 0) modify(l,r,c);
		else printf("%lld\n",query(l,r,c+1));
	}
	return 0;
}

数列分块入门 5 5 5

给出一个长为 n n n 的数列 a 1 … a n a_1 \ldots a_n a1an,以及 n n n 个操作,操作涉及区间开方,区间求和。

这是线段树的经典题,由于每个数开方后最后一定会变成 1 1 1,这个时候再开方就没有意义了,所以我们维护每一个块中 1 1 1 的个数,如果等于块长,就没有必要再开方了。不满足块中都是 1 1 1 的块,就暴力开方即可。

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#define re register
#define drep(a,b,c) for(re int a(b) ; a>=(c) ; --a)
#define rep(a,b,c) 	for(re int a(b) ; a<=(c) ; ++a)
using namespace std;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch == '-') f=-1 ; ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
inline void print(int x){
	if(x < 0) putchar('-'),x = -x;
	if(x >= 10) print(x / 10);
	putchar(x % 10 + '0');
}
const int M = 1e5+10;
int n,B;
int a[M],cnt[M],res[M];
inline void modify(int l,int r){
	int idl = l/B,idr = r/B;
	if(idl == idr){
		rep(i,l,r){
			if(a[i] == 2 || a[i] == 3) cnt[idl]++;
			res[idl] -= a[i],a[i] = sqrt(a[i]),res[idl] += a[i];
		}
	}
	else{
		rep(i,l,(idl+1)*B-1){
			if(a[i] == 2 || a[i] == 3) cnt[idl]++;
			res[idl] -= a[i],a[i] = sqrt(a[i]),res[idl] += a[i];
		}
		rep(id,idl+1,idr-1){
			if(cnt[id] == B) continue;
			rep(i,id*B,(id+1)*B-1){
				if(a[i] == 2 || a[i] == 3) cnt[id]++;
				res[id] -= a[i],a[i] = sqrt(a[i]),res[id] += a[i];
			}
		}
		rep(i,idr*B,r){
			if(a[i] == 2 || a[i] == 3) cnt[idr]++;
			res[idr] -= a[i],a[i] = sqrt(a[i]),res[idr] += a[i];
		}
	}
}
inline int query(int l,int r){
	int idl = l/B,idr = r/B;
	int ans = 0;
	if(idl == idr) rep(i,l,r) ans += a[i];
	else{
		rep(i,l,(idl+1)*B-1) ans += a[i];
		rep(id,idl+1,idr-1) ans += res[id];
		rep(i,idr*B,r) ans += a[i];
	}
	return ans;
}
signed main(){
	n = read();
	B = sqrt(n);
	rep(i,1,n) a[i] = read();
	rep(i,1,n) cnt[i/B] += (a[i]==1),res[i/B] += a[i];
	while(n--){
		int op = read(),l = read(),r = read(),c = read();
		if(op == 0) modify(l,r);
		else printf("%d\n",query(l,r));
	}
	return 0;
}

数列分块入门 6 6 6

给出一个长为 n n n 的数列,以及 n n n 个操作,操作涉及单点插入,单点询问,数据随机生成。

由于操作中有单点插入,可以想到用 v e c t o r vector vector 来维护每一个块的信息。类似于求区间第 k k k 小的数的思想,我们可以找到当前数该插入的位置,具体从第一个块开始找,将要插入的位置与这个块 v e c t o r vector vector s i z e size size 比较,如果大于,就接着往后找,小于说明要插入的位置就在这个块中。这道题数据是随机生成,理论上不重构也能过,但为了保险,我们在一个块的长度大于某个值的时候,就可以将整个数列重构,这样能保证每块都是 n \sqrt n n 的复杂度。

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <vector>
#define re register
#define drep(a,b,c) for(re int a(b) ; a>=(c) ; --a)
#define rep(a,b,c) 	for(re int a(b) ; a<=(c) ; ++a)
using namespace std;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch == '-') f=-1 ; ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
inline void print(int x){
	if(x < 0) putchar('-'),x = -x;
	if(x >= 10) print(x / 10);
	putchar(x % 10 + '0');
}
const int M = 2e5+10;
typedef long long ll;
int n,B,siz;
int a[M],base[M];
vector<int> v[M];
inline void rebuild(){
	B = sqrt(siz);
	int cnt = 0,id = 0;
	while(v[id].size()){
		for(auto it : v[id]) a[++cnt] = it;
		v[id].clear();
		id++;
	}
	rep(i,1,siz) v[i/B].push_back(a[i]);
}
inline void modify(int pos,int x){
	int id = 0;
	while(v[id].size() < pos) pos -= v[id].size(),id++;
	v[id].insert(v[id].begin()+pos-1,x);
	siz++;
	if(v[id].size() > 3*B) rebuild();
}
inline int query(int pos){
	int id = 0;
	while(v[id].size() < pos) pos -= v[id].size(),id++;
	return v[id][pos-1];
}
signed main(){
	n = read();
	B = sqrt(n);
	rep(i,1,n) a[i] = read();
	rep(i,1,n){
		v[i/B].push_back(a[i]);
		siz++;
	}
	rep(i,1,n){
		int op = read(),l = read(),r = read(),c = read();
		if(op == 0) modify(l,r);
		else printf("%d\n",query(r));
	}
	return 0;
}

数列分块入门 7 7 7

给出一个长为 n n n 的数列,以及 n n n 个操作,操作涉及区间乘法,区间加法,单点询问。

很经典的线段树题目,如果用分块来做的话也是一样,维护两个标记,一个是加法,一个是乘法,注意要乘法的优先级大于加法。其余操作类似线段树的下传。

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <vector>
#define re register
#define int long long
#define drep(a,b,c) for(re int a(b) ; a>=(c) ; --a)
#define rep(a,b,c) 	for(re int a(b) ; a<=(c) ; ++a)
using namespace std;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch == '-') f=-1 ; ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
inline void print(int x){
	if(x < 0) putchar('-'),x = -x;
	if(x >= 10) print(x / 10);
	putchar(x % 10 + '0');
}
const int M = 1e5+10;
const int mod = 10007;
typedef long long ll;
int n,B,siz;
int a[M],base[M],mul[M];
inline int query(int pos) { return (a[pos]*mul[pos/B]%mod+base[pos/B])%mod; }
inline void add(int l,int r,int x){
	int idl = l/B,idr = r/B;
	if(idl == idr){
		rep(i,max(1ll,idl*B),min(n,(idl+1)*B-1)) a[i] = query(i);
		base[idl] = 0,mul[idl] = 1;
		rep(i,l,r) a[i] = (a[i] + x)%mod;
	}
	else{
		rep(i,max(1ll,idl*B),min(n,(idl+1)*B-1)) a[i] = query(i);
		base[idl] = 0,mul[idl] = 1;
		rep(i,l,(idl+1)*B-1) a[i] = (a[i] + x)%mod;
		
		rep(id,idl+1,idr-1) base[id] = (base[id] + x)%mod;
		
		rep(i,idr*B,min(n,(idr+1)*B-1)) a[i] = query(i);
		base[idr] = 0,mul[idr] = 1;
		rep(i,idr*B,r) a[i] = (a[i] + x)%mod;
	}
}
inline void tim(int l,int r,int x){
	int idl = l/B,idr = r/B;
	if(idl == idr){
		rep(i,max(1ll,idl*B),min(n,(idl+1)*B-1)) a[i] = query(i);
		base[idl] = 0,mul[idl] = 1;
		rep(i,l,r) a[i] = a[i] * x % mod;
	}
	else{
		rep(i,max(1ll,idl*B),min(n,(idl+1)*B-1)) a[i] = query(i);
		base[idl] = 0,mul[idl] = 1;
		rep(i,l,(idl+1)*B-1) a[i] = a[i] * x % mod;
		
		rep(id,idl+1,idr-1) base[id] = base[id]*x%mod,mul[id] = mul[id]*x%mod;
		
		rep(i,idr*B,min(n,(idr+1)*B-1)) a[i] = query(i);
		base[idr] = 0,mul[idr] = 1;
		rep(i,idr*B,r) a[i] = a[i] * x % mod;
	}
}
signed main(){
	n = read();
	B = sqrt(n);
	rep(i,1,n) a[i] = read();
	rep(i,1,n) mul[i/B] = 1;
	rep(i,1,n){
		int op = read(),l = read(),r = read(),c = read();
		if(op == 0) add(l,r,c);
		if(op == 1) tim(l,r,c);
		if(op == 2) printf("%lld\n",query(r));
	}
	return 0;
}

数列分块入门 8 8 8

给出一个长为 n n n 的数列,以及 n n n 个操作,操作涉及区间询问等于一个数 c c c 的元素,并将这个区间的所有元素改为 c c c

很神奇的一道题,难点在于查询。我们可以发现,在询问过后,大块都会变成一个数,在一些询问后,数列可能会只剩下几段不同的区间了。我们考虑如何进行分块,维护每个块是否只有一个权值,区间操作的时候,如果这个块都是一个权值,那么直接 O ( 1 ) O(1) O(1) 统计答案,否则暴力统计答案。不完整的块也是暴力统计答案。这样复杂度是能过的,具体证明本人不会。

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <vector>
#define re register
#define drep(a,b,c) for(re int a(b) ; a>=(c) ; --a)
#define rep(a,b,c) 	for(re int a(b) ; a<=(c) ; ++a)
using namespace std;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch == '-') f=-1 ; ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
inline void print(int x){
	if(x < 0) putchar('-'),x = -x;
	if(x >= 10) print(x / 10);
	putchar(x % 10 + '0');
}
const int M = 1e5+10;
const int mod = 10007;
typedef long long ll;
int n,B,siz;
int v[M],tag[M],bl[M];
inline void reset(int x){
	if(tag[x] == -1) return;
	for(re int i=(x-1)*B+1 ; i<=x*B ; ++i) v[i] = tag[x];
	tag[x] = -1;
}
inline int query(int a,int b,int c){
	int ans = 0;
	reset(bl[a]);
	for(re int i(a) ; i<=min(bl[a]*B,b) ; ++i){
		if(v[i] != c) v[i] = c;
		else ans++;
	}
	if(bl[a] != bl[b]){
		reset(bl[b]);
		for(re int i=(bl[b]-1)*B+1 ; i<=b ; ++i){
			if(v[i] != c) v[i] = c;
			else ans++;
		}
	}
	for(re int i(bl[a]+1) ; i<=bl[b]-1 ; ++i){
		if(tag[i] != -1){
			if(tag[i] != c) tag[i] = c;
			else ans += B;
		}
		else{
			for(re int j=(i-1)*B+1 ; j<=i*B ; ++j){
				if(v[j] != c) v[j] = c;
				else ans++;
			}
			tag[i] = c;
		}
	}
	return ans;
}
signed main(){
	n = read();
	B = sqrt(n);
	memset(tag,-1,sizeof(tag));
	rep(i,1,n) v[i] = read();
	rep(i,1,n) bl[i] = (i-1)/B+1;
	rep(i,1,n){
		int l = read(),r = read(),c = read();
		printf("%d\n",query(l,r,c));
	}
	return 0;
}

数列分块入门 9 9 9

给出一个长为 n n n 的数列,以及 n n n 个操作,操作涉及询问区间的最小众数。

也是一个很神仙的题目。考虑到一个区间的众数,可能是中间大块的众数,或者是两边小块的某个元素。我们可以先预处理出 a n s [ i ] [ j ] ans[i][j] ans[i][j] 表示第 i i i 个块到第 j j j 个块的最小众数是谁,同时我们需要记录一个前缀和 s [ i ] [ j ] s[i][j] s[i][j] 表示前 i i i 个块中, j j j 出现的次数。这两个都可以在 n n n 根号级别下预处理出来。

然后看怎么统计答案。对于中间的大块,可以直接 O ( 1 ) O(1) O(1) 找出最小的众数是谁,因为已经提前预处理过了。对于两边的小块,对于一个数 a [ i ] a[i] a[i],它出现的次数是 c n t [ a [ i ] ] + s [ i d r − 1 ] [ a [ i ] ] − s [ i d l ] [ a [ i ] ] cnt[a[i]]+s[idr-1][a[i]]-s[idl][a[i]] cnt[a[i]]+s[idr1][a[i]]s[idl][a[i]],这是一个类似前缀和做差的操作,其中 c n t [ a [ i ] ] cnt[a[i]] cnt[a[i]] 表示在两边的小块中 a [ i ] a[i] a[i] 出现的次数。这样我们直接比较出现的次数即可。

注意本题需要离散化。

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#define re register
#define drep(a,b,c) for(re int a(b) ; a>=(c) ; --a)
#define rep(a,b,c) 	for(re int a(b) ; a<=(c) ; ++a)
using namespace std;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch == '-') f=-1 ; ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
inline void print(int x){
	if(x < 0) putchar('-'),x = -x;
	if(x >= 10) print(x / 10);
	putchar(x % 10 + '0');
}
const int M = 1e5+10;
const int N = 1010;
int n,B;
int ans[N][N],s[N][M];
int a[M],lsh[M],cnt[M];
inline int query(int l,int r){
	int idl = l/B,idr = r/B;
	memset(cnt,0,sizeof(cnt));
	int num = ((1ll<<31)-1),cnum = 0;
	if(idl == idr){
		rep(i,l,r){
			cnt[a[i]]++;
			if(cnt[a[i]] > cnum || (cnt[a[i]] == cnum && a[i] < num)) num = a[i],cnum = cnt[a[i]];
		}
	}
	else{
		rep(i,l,(idl+1)*B-1) cnt[a[i]]++;
		rep(i,idr*B,r) cnt[a[i]]++;
		num = ans[idl+1][idr-1];
		cnum = s[idr-1][num] - s[idl][num] + cnt[num];
		rep(i,l,(idl+1)*B-1){
			int ccnt = cnt[a[i]] + s[idr-1][a[i]] - s[idl][a[i]];
			if(ccnt > cnum || (ccnt == cnum && a[i] < num)) num = a[i],cnum = ccnt;
		}
		rep(i,idr*B,r){
			int ccnt = cnt[a[i]] + s[idr-1][a[i]] - s[idl][a[i]];
			if(ccnt > cnum || (ccnt == cnum && a[i] < num)) num = a[i],cnum = ccnt;
		}
	}
	return lsh[num];
}
inline void init(){
	for(re int l(B) ; l<=n ; l+=B){
		memset(cnt,0,sizeof(cnt));
		int tmp = 0;
		for(re int r(l) ; r<=n ; ++r){
			cnt[a[r]]++;
			if(cnt[a[r]] > cnt[tmp] || (cnt[a[r]] == cnt[tmp] && a[r] < tmp)) tmp = a[r];
			if(r%B == B-1) ans[l/B][r/B] = tmp;
		}
	}
	memset(cnt,0,sizeof(cnt));
	rep(i,1,n){
		if(i%B == 0) rep(j,1,n) s[i/B][j] = s[i/B-1][j];
		s[i/B][a[i]]++;
	}
}
signed main(){
	n = read();
	B = sqrt(n);
	rep(i,1,n) lsh[i] = a[i] = read();
	sort(lsh+1,lsh+n+1);
	rep(i,1,n) a[i] = lower_bound(lsh+1,lsh+n+1,a[i])-lsh;
	init();
	rep(i,1,n){
		int l = read(),r = read();
		printf("%d\n",query(l,r));
	}
	return 0;
}
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值