我上集训的前3天都是 h z w e r hzwer hzwer在讲课。不知为什么, h z w e r hzwer hzwer老师特别喜欢分块,拿分块给我们洗脑,说什么:“主席树,线段树,平衡树,都不会,就用分块”。
那我就来讲讲分块⑧。那么,什么是分块呢?
主体思想
我们知道,有一些暴力是修改 O ( 1 ) O(1) O(1),求和 O ( n ) O(n) O(n)(【模板】数组),有一些操作是修改 O ( n ) O(n) O(n),求和 O ( 1 ) O(1) O(1)(【模板】前缀和),但是我们能不能平衡一下呢?
树状数组&线段树 是做到了 O ( l o g n ) O(logn) O(logn),两个都是。那么,有没有更暴力,但是有不那么暴力,而且好写的做法呢?那就是分块了。图示:
把序列分成 n \sqrt{n} n个块。然后我们怎么做操作呢?比如说区间求和&区间加法操作?
(Q:woc这个是讲线段树要讲好久才讲到的操作诶?真的现在就讲?)
(A:不怕,分块比线段树好写的多)
比如说我们要查询红色部分的和。对于跨过的完整快(绿色部分),我们直接维护块里的和,
然后加上即珂。对于蓝色部分,我们就暴力求和,反正也不多,最多
2
n
2\sqrt{n}
2n个。所以就保证了一次求和的复杂度是
O
(
n
)
O(\sqrt{n})
O(n)。
对于加上红色的部分,我们只要对块内标记做修改即珂。然后对于蓝色的部分,我们只要暴力 + = += +=就珂以了。这样也是 O ( n ) O(\sqrt{n}) O(n)的。
总的来说,分块的思想就是:
完 整 块 标 记 , 不 完 整 暴 力 \color{#ff0000}完整块标记,不完整暴力 完整块标记,不完整暴力
来题!
h z w e r hzwer hzwer在 l i b r e o j libreoj libreoj上发了不少分块的入门题目,找“分块”,有且只有那几个题。由于我也是刚学,就找几个水的玩玩吧。
数列分块2
题意:支持两种操作,区间加,区间查询多少数 < = x <=x <=x。
做法:
分块。块维护一个标记,表示这个块被整体加了多少。对于区间加的操作就按上面的方法来就珂以了。
区间查询多少数 < = x <=x <=x,我们发现,对于一个块,这个块内部的顺序是不会影响整体答案的。所以我们把原数组拷贝一个副本,然后对于同一个块,就排一下序。对于整个块,就只要 l o w e r b o u n d lower_bound lowerbound一下就能求出答案了。对于不完整的块,我们就用原来的数组,暴力算一下即珂。
代码:
#include<bits/stdc++.h>
using namespace std;
namespace Flandle_Scarlet
{
#define N 100100
#define int long long
void R1(int &x)
{
x=0;char c=getchar();int f=1;
while(c<'0' or c>'9') f=(c=='-')?-1:1,c=getchar();
while(c>='0' and c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
x=(f==1)?x:-x;
}
int n,a[N];
int block;
void Input()
{
R1(n);
block=sqrt(n+0.5);
for(int i=1;i<=n;++i) R1(a[i]);
}
int tag[N];//加法标记
vector<int> v[1010];//原数组的拷贝,为了方便分块,用了vector
#define pos(x) ((x-1)/block+1)
#define Start(b) ((b-1)*block+1)
#define End(b,lim) (min(b*block,lim))
//很多时候End都是要限制一下的,所以加上了lim
void cls(int x)
{
v[x].clear();
for(int i=Start(x);i<=End(x,n);++i)
//比如说这里最后一个块不能超出n,所以就要和n取min
{
v[x].push_back(a[i]);
}
sort(v[x].begin(),v[x].end());//排序
}
void Add(int l,int r,int val)
{
for(int i=l;i<=End(pos(l),r);++i) a[i]+=val;
cls(pos(l));//暴力
if (pos(l)!=pos(r))//这里判这个的原因是,
//如果l和r同一个块,那么就只会有一段不是完整的块,就会算重
{
for(int i=Start(pos(r));i<=r;++i)
{
a[i]+=val;
}
cls(pos(r));
}
for(int i=pos(l)+1;i<=pos(r)-1;++i)
{
tag[i]+=val;
}//整个块就打标记
}
int Query(int l,int r,int k)
{
int ans=0;
for(int i=l;i<=End(pos(l),r);++i)//暴力统计
{
if (a[i]+tag[pos(l)]<k) ++ans;
}
if (pos(l)!=pos(r))
{
for(int i=Start(pos(r));i<=r;++i)
{
if (a[i]+tag[pos(r)]<k) ++ans;
}
}
for(int i=pos(l)+1;i<=pos(r)-1;++i)
{
int x=k-tag[i];//注意:要k-tag[i],原因是这个块里被加上了tag[i],但是并没有体现在副本数组v中
ans+=lower_bound(v[i].begin(),v[i].end(),x)-v[i].begin();
}
return ans;
}
void Soviet()
{
for(int i=1;i<=n;++i)
{
v[pos(i)].push_back(a[i]);
}
for(int i=1;i<=pos(n);++i)
{
sort(v[i].begin(),v[i].end());
}
for(int i=1;i<=n;++i)
{
int o,l,r,c;
R1(o),R1(l),R1(r),R1(c);
if (o==0)
{
Add(l,r,c);
}
else
{
printf("%lld\n",Query(l,r,c*c));
}
}
}
void IsMyWife()
{
Input();
Soviet();
}
#undef int //long long
};
int main()
{
Flandle_Scarlet::IsMyWife();
return 0;
}