分块学习基础

分块介绍

有一种代码比树状数组、线段树简单,效率比暴力法高的算法,称为“分块”,它能以 O ( m ∗ n 0.5 ) {O(m*n^{0.5})} O(mn0.5)的复杂度解决“区间修改+区间查询”问题。简单地说,分块是用线段树的“分区”思想改良的暴力法;它把数列分成很多“块”,对涉及到的块做整体性的维护操作(类似于线段树的lazy-tag),而不是像普通暴力法那样处理整个数列,从而提高了效率。用一个长度为n的数组来存储 n {n} n个数据,把它分为 t {t} t块,每块长度为 n / t {n/t} n/t .

分块处理思想

在处理区间问题时需要处理两种块:整块(中间)和碎块(两端).此时整块整体处理,而碎块用暴力处理即可,单次复杂度 O ( n 0.5 ) {O(n^{0.5})} O(n0.5),处理 1 0 5 {10^5} 105级的数据绰绰有余(加常数优化效果更佳),在暴力算法中很可观.

分块模板

线段树分块模板

(数据小,可以用它搞qwq)

#include<bits/stdc++.h>
#define N 200005
#define int long long
#define in read()
using namespace std;

int n,m,t,block,st[N],ed[N],pos[N];
int a[N],sum[N],add[N];

inline int in{
	int i=0,f=1;char ch;
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)){i=(i<<3)+(i<<1)+(ch-'0');ch=getchar();}
	return i*f;
}

inline void Init()//分块核心
{
	block=sqrt(n);
	t=n/block;
	if(n%block)t++;
	for(int i=1;i<=t;i++)
	{
		st[i]=(i-1)*block+1;
		ed[i]=i*block;
	}
	ed[t]=n;
	for(int i=1;i<=n;i++)
	pos[i]=(i-1)/block+1;
	for(int i=1;i<=t;i++)
		for(int j=st[i];j<=ed[i];j++)
		sum[i]+=a[j];//预处理区间和
	return;
}

inline void addx(int l,int r,int d)//区间加
	int p=pos[l],q=pos[r];
	if(p==q)
	{
		for(int i=l;i<=r;i++)a[i]+=d;
		sum[p]+=d*(r-l+1);
	}
	else
	{
		for(int i=p+1;i<=q-1;i++)add[i]+=d;
		for(int i=l;i<=ed[p];i++)a[i]+=d;
		sum[p]+=d*(ed[p]-l+1);
		for(int i=st[q];i<=r;i++)a[i]+=d;
		sum[q]+=d*(r-st[q]+1);
	}
	return;
}

inline int query(int l,int r)//查询
{
	int p=pos[l],q=pos[r],ans=0;
	if(p==q)
	{
		for(int i=l;i<=r;i++)ans+=a[i];
		ans+=add[p]*(r-l+1);
	}
	else
	{
		for(int i=p+1;i<=q-1;i++)ans+=sum[i]+add[i]*(ed[i]-st[i]+1);
		for(int i=l;i<=ed[p];i++)ans+=a[i];
		ans+=add[p]*(ed[p]-l+1);
		for(int i=st[q];i<=r;i++)ans+=a[i];
		ans+=add[q]*(r-st[q]+1);
	}
	return ans;
}

signed main()
{
	int x,y,z;
	n=in,m=in;;
	for(int i=1;i<=n;i++)
	a[i]=in;
	Init();
	for(int i=1;i<=m;i++)
	{
		int opt=in;
		if(opt==1)
		{
			x=in,y=in,z=in;
			addx(x,y,z);
		}
		if(opt==2)
		{
			x=in,y=in;
			printf("%lld\n",query(x,y));
		}
	}
	return 0;
}

接下来是例题.

1.教主的魔法

这道题就是用一个无序数组 b [ ] {b[]} b[]维护各个块中每个数从小到大保持有序+二分查找即可.
时间复杂度: O ( n + m ∗ ( n 0.5 l o g   n 0.5 + l o g   n 0.5 ) ≈ n 1.5 l o g n 0.5 ) {O(n+m*(n^{0.5}log~n^{0.5}+log~n^{0.5})≈n^{1.5}logn^{0.5})} O(n+m(n0.5log n0.5+log n0.5)n1.5logn0.5)

附上代码:

#include<bits/stdc++.h>
#define N 2000005
#define int long long
#define in read()
using namespace std;

int n,m,t,block,st[N],ed[N],pos[N];
int a[N],b[N],add[N];

inline int in{
	int i=0,f=1;char ch;
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)){i=(i<<3)+(i<<1)+(ch-'0');ch=getchar();}
	return i*f;
}

inline void Init()
{
	block=sqrt(n);
	t=n/block;
	if(n%block)t++;
	for(int i=1;i<=t;i++)
	{
		st[i]=(i-1)*block+1;
		ed[i]=i*block;
	}
	ed[t]=n;
	for(int i=1;i<=n;i++)
	pos[i]=(i-1)/block+1;
	for(int i=1;i<=t;i++)
	sort(b+st[i],b+ed[i]+1);
	return;
}

inline void addx(int l,int r,int d)
{
	int p=pos[l],q=pos[r];
	if(p==q)
	{
		for(int i=st[p];i<=ed[p];i++)
		{
			if(i>=l&&i<=r)a[i]+=d,
			b[i]=a[i];
		}
		sort(b+st[p],b+ed[p]+1);
	}
	else
	{
		for(int i=p+1;i<=q-1;i++)add[i]+=d;
		for(int i=st[p];i<=ed[p];i++)
		{
			if(i>=l)a[i]+=d;
			b[i]=a[i];
		}
		sort(b+st[p],b+ed[p]+1);
		for(int i=st[q];i<=ed[q];i++)
		{
			if(i<=r)a[i]+=d;
			b[i]=a[i];
		}
		sort(b+st[q],b+ed[q]+1);
	}
	return;
}

inline int query(int l,int r,int d)
{
	int p=pos[l],q=pos[r],ans=0;
	if(l==r)return (b[l]>=d-add[p]? 1 : 0);
	if(p==q)
	{
		int ll=st[p],rr=ed[p]+1;
		while(ll<rr)
		{
			int mi=(ll+rr)>>1;
			if(b[mi]>=d-add[p])rr=mi;
			else ll=mi+1;
		}
		ans+=r-rr+1;
	}
	else 
	{
		for(int i=p+1;i<=q-1;i++)
		{
			int ll=st[i],rr=ed[i]+1;
			while(ll<rr)
			{
				int mi=(ll+rr)>>1;
				if(b[mi]>=d-add[i])rr=mi;
				else ll=mi+1;
			}
			ans+=ed[i]-rr+1;
		}
		for(int i=l;i<=ed[p];i++)
		if(b[i]>=d-add[p])ans++;
		for(int i=st[q];i<=r;i++)
		if(b[i]>=d-add[q])ans++;
	}
	return ans;
}

signed main()
{
//	freopen("教主的魔法.in","r",stdin);
	int x,y,z;
	n=in,m=in;
	for(int i=1;i<=n;i++)
	{
		a[i]=in;
		b[i]=a[i];
	}
	Init();
	for(int i=1;i<=m;i++)
	{
		char opt;
		cin>>opt;
		if(opt=='M')
		{
			x=in,y=in,z=in;
			addx(x,y,z);
//			for(int i=1;i<=n;i++)
//			cout<<b[i]+add[pos[i]]<<" ";
//			cout<<endl;
		}
		if(opt=='A')
		{
			x=in,y=in,z=in;
			printf("%lld\n",query(x,y,z));
		}
	}
	return 0;
}

2.弹飞绵羊

这道题需要用两个数组 t o [ i ] {to[i]} to[i] s t e p [ i ] {step[i]} step[i]分别表示从 i {i} i开始弹到下一个块的第一个点的位置以及所需的次数.更新时由于每个块互不影响,所以单次更新只需对所更新的弹簧所在的块进行更新即可.对于查询,即只需进行模拟,并利用已处理的数组进行一次一个区间的快速跳跃。
时间复杂度: O ( m n 0.5 ) {O(mn^{0.5})} O(mn0.5).

#include<bits/stdc++.h>
#define N 200005
#define in read()
#define re register 
using namespace std;

int n,m,a[N],ans;
int block,t,st[N],ed[N],to[N],stp[N],pos[N];
int jn1[N];

inline int in{
	int i=0;char ch;
	while(!isdigit(ch)){ch=getchar();}
	while(isdigit(ch)){i=(i<<3)+(i<<1)+(ch-'0');ch=getchar();}
	return i;
}

inline void Init()
{
	block=sqrt(n);
	
	t=n/block;
	if(n%block)t++;
	for(re int i=1;i<=t;i++){
		st[i]=(i-1)*block+1;
		ed[i]=i*block;
	}
	ed[t]=n;
	for(re int i=1;i<=n;i++)
	pos[i]=(i-1)/block+1;
}
inline void update(int l,int r)
{
	for(re int i=r;i>=l;i--){	
		if(i+a[i]>ed[pos[i]]){
		stp[i]=1,to[i]=i+a[i];}
		else{
		stp[i]=stp[i+a[i]]+1,to[i]=to[i+a[i]];}
	}
	return;
}
inline int work(int x)
{
	int ans=0;
	while(x<=n){
	ans+=stp[x];
	x=to[x];
	}
	return ans;
}
int main()
{
//	freopen("弹飞绵羊.in","r",stdin);
//	freopen("弹飞绵羊.out","w",stdout);
	n=in;
	for(re int i=1;i<=n;i++)
	a[i]=in;
	Init();
	update(1,n);
	m=in;
	for(re int i=1;i<=m;i++){
		int x,y,z;
		x=in,y=in,y++;
		if(x==1)
		cout<<work(y)<<endl;
		else{
			z=in,a[y]=z;
			update(st[pos[y]],ed[pos[y]]);
		}
	}
	return 0;
}

完结撒花qwq.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

liaoxiyan123

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值