分块入门

线段树是把数组结构化为一棵树,然后大区间包含小区间的更新与查询。为分块是把数组结构化为若干个连续的块,然后进行操作。小范围暴力,大范围分块更新,块的大小一般取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 

1 2 3 4 
2 3 
1 3 4 5 
2 5 
1 5 8 5


5 3 7 7 5 8 5 

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 
样例输出 

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 
样例输出 


0


数据范围: 
这里写图片描述

题解:

这题还是要先把数据存容器里,然后分下块,对于查找每个区间小于某个数的,我们通常采用排序+二分的方法。其他的都和上题差不多了。

#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次操作的结果。 
样例输入 

1 3 5 4 2 
1 3 2 
2 5 2 
3 4 4 
1 3 3 
4 5 4 
样例输出 





数据范围 
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;
}

 

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值