线段树是把数组结构化为一棵树,然后大区间包含小区间的更新与查询。为分块是把数组结构化为若干个连续的块,然后进行操作。小范围暴力,大范围分块更新,块的大小一般取sqrt(n)。
首先我们需要认识两个数组。
bl[i] //当前下标i属于哪一块
tag[i] //块,可以暂时理解为线段树中的lazy标记
如下图所示我们对长度为7的数组进行分块,每个块大小为sqrt(7)=2.
如果我们对[li,ri]进行操作,则首先对第a[2]和a[7]更新,然后对[L,R]中的块tag[2]和tag[3]更新。
理解完就可以做题了。
问题 A: 小Z的课堂检测
时间限制: 1 Sec
内存限制: 128 MB
题目描述
大家都知道小Z的课总是十分快的(鬼知道为什么),然后我们阿M同学总是在上课时处于神游状态亦或是休眠状态,所以她对小Z到底讲了什么是一无所知。然而,小Z总是很坏地打断阿M的休眠状态,并问她问题。作为阿M的开黑好伙伴,你当然不希望阿M同学翻车(不然下一个回答问题的人就是你啦)。所以你需要编写个程序帮助阿M求小Z对于知识点到底讲的档次有多深。已知小Z在课上总会扯到涉及到N个知识点,小Z会进行M个动作(讲课或是提问阿M)。由于小Z比较古灵精怪,所以小Z的讲课时只会讲连续的知识点,并且对于这段区间内的知识点都提升一样的档次。而且,小Z也比较懒,所以小Z只会问阿M关于某一个知识点的了解程度。
输入
第一行读入N,表示小Z要涉及到N个知识点
第二行读入A[1],A[2]……A[N-1],A[N]表示小Z上几节课已经把第i个知识点的 难度提升到A[i]的难度
第三行读入M,表示小Z要进行M个动作
接下来M行,读入Choice
若Choice=1,则表示小Z要讲课啦
接下来读入L,R,X 表示小Z要对L到R这些连续的知识点提升难度X
若Choice=2,则表示小Z要提问啦
接下来读入K,表示小Z问阿M第K个知识点他已经讲到哪个难度了
输出
每行输出一个数表示阿M应该回答的正确答案
样例输入
10
1 2 3 4 5 6 7 8 9 10
5
1 2 3 4
2 3
1 3 4 5
2 5
1 5 8 5
7
5 3 7 7 5 8 5
9
1 2 7 -1
2 1
2 2
1 2 3 1
1 2 7 2
2 2
1 3 3 -1
2 3
2 1
样例输出
7
5
5
2
5
8
5
数据范围
对于50%的数据,N<=1000,M<=1000
对于100%的数据,N<=100000,M<=100000 |X|<=50000
|A[i]|<=50000;
经典的数据结构题目,也是分块最基础的题目。利用上面我们学习的东西,可以轻松的写出代码。
/*
bl[i] //当前i属于哪一块
tag[i] //块
a[i] //原始数组
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll sq;
ll tag[500005],a[500005],bl[500005],n;
void update(ll l,ll r,ll val)
{
for(ll i=l;i<=min(r,bl[l]*sq);i++) //左
a[i]+=val;
for(int i=bl[l]+1;i<=bl[r]-1;i++) //中间的块
tag[i]+=val;
if(bl[l]!=bl[r])
for(int i=(bl[r]-1)*sq+1;i<=r;i++) a[i]+=val; //右
}
ll search(int pos)
{
return (a[pos]+tag[bl[pos]]);
}
int main()
{
cin>>n;sq=sqrt(n);
for(int i=1;i<=n;i++)
{
cin>>a[i];bl[i]=(i-1)/sq+1;cout<<bl[i]<<endl;
}
int m;cin>>m;
while(m--)
{
int q;cin>>q;
if(q==1)
{
ll l,r,p;cin>>l>>r>>p;
update(l,r,p);
}
else
{
int x;cin>>x;
cout<<search(x)<<endl;
}
}
return 0;
}
问题 B: 小M的简单题
时间限制: 1 Sec
内存限制: 128 MB
题目描述
小M是某知名高中的学生,有一天,他请他的n个同学吃苹果,同学们排成一行,且手中已经有一些苹果。为了表示他的大方,有时他会给l到r的同学x个苹果,但为了了解分配的情况,有时他会询问l到r的同学中拥有的苹果数小于x的人的个数。现在,小M想让你帮他解决这道简单题。
输入
第一行:两个整数n,m表示n个同学,m组询问。
第二行:n个数,a[1],a[2]…a[n],a[i]表示第i个同学一开始手中的苹果数。(0<=a[i]<=3e4)
第3~m+2行:每行表示一组询问,格式为C l r x表示给l到r的同学x个苹果,或者Q l r x表示询问l到r的同学中拥有的苹果数小于x的人的个数。(1<=l<=r<=n,0<=x<=3e4)
输出
每行一个数,输出l到r的同学中拥有的苹果数小于x的人的个数。
样例输入
5 5
1 6 3 2 3
Q 1 3 3
C 1 2 2
Q 3 4 3
C 2 3 1
Q 2 3 4
5 4
2 3 1 3 4
C 4 5 3
C 1 5 1
C 2 3 2
Q 1 3 4
样例输出
1
1
0
1
数据范围:
题解:
这题还是要先把数据存容器里,然后分下块,对于查找每个区间小于某个数的,我们通常采用排序+二分的方法。其他的都和上题差不多了。
#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
typedef long long ll;
vector<int> v[30004];
int a[30004];
int tag[30004];
int bl[30004];
int blo;
int n;
void reset(int x)
{
v[x].clear();
for(int i=(x-1)*blo+1;i<=min(x*blo,n);i++)
{
v[x].push_back(a[i]);
}
sort(v[x].begin(),v[x].end());
}
void update(int l,int r,int p)
{
for(int i=l;i<=min(r,bl[l]*blo);i++)
{
a[i]+=p;
}
reset(bl[l]);
for(int i=bl[l]+1;i<=bl[r]-1;i++)
{
tag[i]+=p;
}
if(bl[l]!=bl[r])
{
for(int i=(bl[r]-1)*blo+1;i<=r;i++)
{
a[i]+=p;
}
reset(bl[r]);
}
}
int search(int l,int r,int p)
{
int sum=0;
for(int i=l;i<=min(r,bl[l]*blo);i++)
{
if(a[i]+tag[bl[l]]<p)sum++;
}
for(int i=bl[l]+1;i<=bl[r]-1;i++)
{
int t=p-tag[i];
sum+=lower_bound(v[i].begin(),v[i].end(),t)-v[i].begin();
}
if(bl[l]!=bl[r])
for(int i=(bl[r]-1)*blo+1;i<=r;i++)
{
if(a[i]+tag[bl[r]]<p) sum++;
}
return sum;
}
int main()
{
cin>>n;
int m;cin>>m;
blo=sqrt(n);
for(int i=1;i<=n;i++)
{
cin>>a[i];
bl[i]=(i-1)/blo+1;
v[bl[i]].push_back(a[i]);
}
for(int i=1;i<=bl[n];i++)
sort(v[i].begin(),v[i].end());
while(m--)
{
int l,r,p,q;
char ch[5];cin>>ch;
if(ch[0]=='C')
{
cin>>l>>r>>p;
update(l,r,p);
}
else
{
cin>>l>>r>>p;
cout<<search(l,r,p)<<endl;
}
}
return 0;
}
C:
给出一个长为n的数列,以及n个操作,操作涉及区间询问等于一个数c的元素个数,并将这个区间的所有元素改为c。
输入
第一行一个整数n;
接下来的一行n个整数表示原数列;
第3-2+n行 每行输入三个整数 l r c,要求输出数列区间[l,r](两端取到)内等于c的元素个数,并把序列[l,r]改为c。
输出
共n行,第i行一个整数,表示第i次操作的结果。
样例输入
5
1 3 5 4 2
1 3 2
2 5 2
3 4 4
1 3 3
4 5 4
样例输出
0
3
0
0
1
数据范围
N<=100000
题解:分块后注意修改tag标记。
#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
typedef long long ll;
int a[30004];
int tag[30004];
int bl[30004];
int blo;
int n;
void reset(int x)
{
if(tag[x]==-1) return ;
for(int i=(x-1)*blo+1;i<=min(n,x*blo);i++)
{
a[i]=tag[x];
}
tag[x]=-1;
}
int search(int l,int r,int p)
{
int sum=0;
reset(bl[l]);
for(int i=l;i<=min(r,bl[l]*blo);i++)
{
if(a[i]==p) sum++;
a[i]=p;
}
for(int i=bl[l]+1;i<=bl[r]-1;i++)
{
if(tag[i]!=-1)
{
if(tag[i]==p) sum+=blo;
tag[i]=p;
}
else //注意tag[i]==-1时的情况
{
for(int j=(i-1)*blo+1;j<=i*blo;j++)
{
if(a[j]==p) sum++;
a[j]=p;
}
tag[i]=p;
}
}
if(bl[l]!=bl[r])
{
reset(bl[r]);
for(int i=(bl[r]-1)*blo+1;i<=r;i++)
{
if(a[i]==p) sum++;
a[i]=p;
}
}
return sum;
}
int main()
{
cin>>n;
blo=sqrt(n);
for(int i=1;i<=n;i++)
{
cin>>a[i];
bl[i]=(i-1)/blo+1;tag[i]=-1;
}
for(int i=1;i<=n;i++)
{
int l,r,p;
cin>>l>>r>>p;
cout<<search(l,r,p)<<endl;
}
return 0;
}