出处
学习蓝书的时候感觉书上关于分块的题目太少了.而且都是难度较大的一些分块题目,想巩固一下分块方面的知识,就找到了hzwer大佬的分块入门知识介绍.用这篇博客记录一下.
从树状数组到线段树再到分块.都是对区间信息的快速处理来达到想要的效果.树状数组效率最优,可是拓展性实在不高.线段树效率稍微差一点但是拓展性较好,可是在信息不满足区间可加性的情况下代码难度会高很多.而分块效率上最差但是可以接受,且拓展性能十分良好,毕竟人称"优雅的暴力".
所谓分块,就是把一段区间分成一块一块的小区间.采取"大段维护,小段朴素"的思想,在涉及到跨越多块的区间时直接维护块上的信息(这个时候块可以看成一个元素).而在左右零散的元素上直接暴力做.这样子的复杂度如果在块上是O(1)的那么整体就是O(T1+T2)的.T1是块的大小.T2是块的个数.而如何确定块的大小和如何收集块的信息就是我们要集中解决的问题了.
第一题
询问单点和,支持区间加.
前面阐述过了,这些块其实可以看做是一个元素,区间加的时候如果跨越了一整个块,就用一个add数组来记录一下这个块被加过多少,最后累加到a[r]上面就可以了.
代码(时间:105ms)
#pragma GCC optimize(3)
#define LL long long
#define pq priority_queue
#define ULL unsigned long long
#define pb push_back
#define mem(a, x) memset(a, x, sizeof a)
#define pii pair
#define pll pair
#define pdd pair
#define db double
#define fir(i, a, b) for (int i = a; i <= b; ++i)
#define afir(i, a, b) for (int i = a; i >= b; --i)
#define ft first
#define vi vector
#define sd second
#define ALL(a) a.begin(), a.end()
#include
using namespace std;
const int N = 5e4 + 10;
const int mod = 9901;
inline int read() {
int x = 0, f = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-')
f = -1;
ch = getchar();
}
while (ch <= '9' && ch >= '0') {
x = x * 10 + ch - '0';
ch = getchar();
}
return x * f;
}
int n, m, t, a[N], pos[N], L[N], R[N], add[N];
void change(int l, int r, int v) {
int p = pos[l], q = pos[r];
if (p == q) {
fir(i, l, r) a[i] += v;
} else {
fir(i, p + 1, q - 1) add[i] += v;
fir(i, l, R[p]) a[i] += v;
fir(i, L[q], r) a[i] += v;
}
}
int main() {
n = read();
t = sqrt(n);
fir(i, 1, n) a[i] = read();
fir(i, 1, t) {
L[i] = (i - 1) * t + 1;
R[i] = i * t;
}
if (t < sqrt(n))
++t, L[t] = R[t - 1] + 1, R[t] = n;
fir(i, 1, t) fir(j, L[i], R[i]) pos[j] = i;
while (n--) {
int op, l, r, d;
op = read();
l = read();
r = read();
d = read();
if (op)
cout << a[r] + add[pos[r]] << endl;
else
change(l, r, d);
}
return 0;
}
第二题
要求区间加,询问区间小于x有多少个数.
如果说区间是有序的,我们可以用二分在O(log(n))的时间里求出有多少个数. 这个方法配合分块可以解决这个问题.
假设有T1个块,一个块的大小为T2. 那么我们可以给每一个块排序.复杂度(O T1 ×T2×log(T2) ). 修改复杂度(T1+T2). 询问复杂度(T2+T1×log(T2)) 其实T1 = T2 = sqrt(n)的时候复杂度已经够了, 但观察这几个表达式我们可以发现的是,把T1取小一点,T2取大一点会更优(数学不好不太会算具体取多少). 不过这就是分块要解决的问题之一,如何权衡块的大小和块的个数.
来自hzwer大佬的建议(但是我不会写对拍器orz):
分块的调试检测技巧: 可以生成一些大数据,然后用两份分块大小不同的代码来对拍,还可以根据运行时间尝试调整分块大小,减小常数。
具体代码(时间:1406 ms)
#pragma GCC optimize(3)
#define LL long long
#define pq priority_queue
#define ULL unsigned long long
#define pb push_back
#define mem(a,x) memset(a,x,sizeof a)
#define pii pair
#define pll pair
#define pdd pair
#define db double
#define fir(i,a,b) for(int i=a;i<=b;++i)
#define afir(i,a,b) for(int i=a;i>=b;--i)
#define ft first
#define vi vector
#define sd second
#define ALL(a) a.begin(),a.end()
#include
using namespace std;
const int N = 5e4+10;
const int mod = 9901;
inline int read(){
int x = 0,f=1;char ch = getchar();
while(ch<'0'||ch>'9'){
if(ch=='-')f=-1;ch=getchar();}
while(ch<='9'&&ch>='0'){
x=x*10+ch-'0';ch=getchar();}
return x*f;
}
int n,t,a[N],add[N],L[N],R[N],pos[N];
vi vec[250];
void change(int l,int r,int v){
int p = pos[l],q = pos[r];
if(p == q){
fir(i,l,r) a[i] += v;
int j = 0;
fir(i,L[p],R[p]) vec[p][j++] = a[i];
sort(ALL(vec[p]));
}
else{
fir(i,p+1,q-1) add[i] += v;
fir(i,l,R[p]) a[i] += v;
int j = 0;
fir(i,L[p],R[p]) vec[p][j++] = a[i];
sort(ALL(vec[p]));
fir(i,L[q],r) a[i] += v;
j = 0;
fir(i,L[q],R[q]) vec[q][j++] = a[i];
sort(ALL(vec[q]));
}
}
int query(int l,int r,int v){
int p = pos[l],q = pos[r];
int res = 0;
if(p == q){
fir(i,l,r){
if(a[i] < v-add[p]) res ++;
}
}
else{
fir(i,p+1,q-1) res += lower_bound(ALL(vec[i]),v-add[i])-vec[i].begin();
fir(i,l,R[p]){
if(a[i] < v-add[p]) res++;
}
fir(i,L[q],r){
if(a[i] < v-add[q]) res++;
}
}
return res;
}
int main(){
n = read();
fir(i,1,n) a[i] = read();
t = sqrt(n);
fir(i,1,t){
L[i] = (i-1)*t+1;
R[i] = i*t;
}
if(t < n) t++,L[t] = R[t-1]+1,R[t] = n;
fir(i,1,t){
fir(j,L[i],R[i]) pos[j] = i,vec[i].pb(a[j]);
sort(ALL(vec[i]));
}
while(n--){
int op,l,r,d;
op = read(),l = read(),r = read(),d = read();
if(op) cout << query(l,r,d*d) << endl;
else change(l,r,d);
}
return 0;
}
第三题
要求区间加,找前驱(小于x的最大元素)
其实这题和上题差不多.改一点就可以了.不过要注意判断无解的情况,用long long是一种方法,如果用int 的话 要记得初始化答案为 -(1LL << 31) 1直接左移31是会出错的.
代码(时间:2968 ms)
#pragma GCC optimize(3)
#define LL long long
#define pq priority_queue
#define ULL unsigned long long
#define pb push_back
#define mem(a,x) memset(a,x,sizeof a)
#define pii pair
#define pll pair
#define pdd pair
#define db double
#define fir(i,a,b) for(int i=a;i<=b;++i)
#define afir(i,a,b) for(int i=a;i>=b;--i)
#define ft first
#define vi vector
#define sd second
#define ALL(a) a.begin(),a.end()
#include
using namespace std;
const int N = 1e5+10;
const int mod = 9901;
inline int read(){
int x = 0,f=1;char ch = getchar();
while(ch<'0'||ch>'9'){
if(ch=='-')f=-1;ch=getchar();}
while(ch<='9'&&ch>='0'){
x=x*10+ch-'0';ch=getchar();}
return x*f;
}
int n,t,a[N],add[N],L[N],R[N],pos[N];
vi vec[330];
void change(int l,int r,int v){
int p = pos[l],q = pos[r];
if(p == q){
fir(i,l,r) a[i] += v;
int j = 0;
fir(i,L[p],R[p]) vec[p][j++] = a[i];
sort(ALL(vec[p]));
}
else{
fir(i,p+1,q-1) add[i] += v;
fir(i,l,R[p]) a[i] += v;
int j = 0;
fir(i,L[p],R[p]) vec[p][j++] = a[i];
sort(ALL(vec[p]));
fir(i,L[q],r) a[i] += v;
j = 0;
fir(i,L[q],R[q]) vec[q][j++] = a[i];
sort(ALL(vec[q]));
}
}
int query(int l,int r,int v){
int p = pos[l],q = pos[r];
int res = -(1LL << 31);
if(p == q){
fir(i,l,r){
if(a[i] < v-add[p]){
res = max(res,a[i]+add[p]);
}
}
}
else{
fir(i,p+1,q-1){
int cur = lower_bound(ALL(vec[i]),v-add[i])-vec[i].begin();
if(!cur) continue;
res = max(res,vec[i][cur-1]+add[i]);
}
fir(i,l,R[p]){
if(a[i] < v-add[p]){
res = max(res,a[i]+add[p]);
}
}
fir(i,L[q],r){
if(a[i] < v-add[q]){
res = max(res,a[i]+add[q]);
}
}
}
if(res == -(1LL << 31)) return -1;
return res;
}
int main(){
n = read();
fir(i,1,n) a[i] = read();
t = sqrt(n);
fir(i,1,t){
L[i] = (i-1)*t+1;
R[i] = i*t;
}
if(t < n) t++,L[t] = R[t-1]+1,R[t] = n;
fir(i,1,t){
fir(j,L[i],R[i]) pos[j] = i