分块/莫队(模板&&习题)

分块是一种思想,把一个整体划分为若干个小块,对整块整体处理,零散块单独处理。本文主要介绍块状数组——利用分块思想处理区间问题的一种数据结构。

块状数组把一个长度为 n n n 的数组划分为 a a a 块,每块长度为 n / a n/a n/a 。对于一次区间操作,对区间內部的整块进行整体的操作,对区间边缘的零散块单独暴力处理。(所以分块被称为“优雅的暴力”)

这里,块数既不能太少也不能太多。如果太少,区间中整块的数量会很少,我们要花费大量时间处理零散块;如果太多,又会让块的长度太短,失去整体处理的意义。一般来说,我们取块数为$ \sqrt[]{{n}}$ ,这样在最坏情况下,我们要处理接近 n \sqrt[]{{n}} n 个整块,还要对长度为 2 ∗ n 2*\sqrt[]{{n}} 2n 的零散块单独处理,总时间复杂度为 n \sqrt[]{{n}} n 。这是一种根号算法。但实际上,有时候我们要根据数据范围、具体复杂度来确定。

显然,分块的时间复杂度比不上线段树和树状数组这些对数级算法。但由此换来的,是更高的灵活性。与线段树不同,块状数组并不要求所维护信息满足结合律,也不需要一层层地传递标记。

常用的预处理模板:

void init()
{
	int sq = sqrt( n );//开根
	for(int i=1;i<=n;i++)
	{
		bl[i] = (i-1)/sq + 1; //给每个位置i确定所属的块的编号
	}
	for(int i=1;i<=bl[n];i++)
	{
		st[i] = (i-1)*sq + 1;//第i块的开始位置
		ed[i] = i*sq;//第i块的终止位置
	}
	ed[ bl[n] ] = n;//注意最后一块特别处理
}

T1

在这里插入图片描述

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#define LL long long
#define N 101000
#define INF 0x3f3f3f3f 
using namespace std;

LL n,m,a[N],bel[N],sum[N],lazy[N],st[1000],ed[1000],sq,size[1000],ans;

void init()
{
	sq = sqrt(n);
	for(LL i=1;i<=sq;i++)
	{
		st[i] = n / sq * (i-1) + 1;
		ed[i] = n / sq * i;
	}
	ed[sq] = n;
	for(LL i=1;i<=sq;i++)
	{
		for(LL j=st[i];j<=ed[i];j++)
		{
			bel[j] = i;
		}
	}
}

void solve_fenkuai()
{
	cin>>n;
	for(LL i=1;i<=n;i++)
	{
		scanf("%lld",&a[i]);
	}
	init();
	for(int q=1;q<=n;q++)
	{
		LL x,y,k;
		LL op;
		scanf("%lld",&op);
		if( op==0 )
		{
			scanf("%lld %lld %lld",&x,&y,&k);
			if( bel[x] == bel[y] )
			{
				for(int i=x;i<=y;i++)
				{
					a[i] += k;
				}
			}
			else
			{
				for(LL i=x;i<=ed[ bel[x] ];i++)
				{
					a[i] += k;
				}
				for(LL i=st[ bel[y] ];i<=y;i++)
				{
					a[i] += k;
				}
				for(LL i=bel[x]+1;i<=bel[y]-1;i++)
				{
					lazy[i] += k;
				}
			}
		}
		
		
		else
		{
			scanf("%lld %lld %lld",&x,&y,&k);
			printf("%lld\n",a[y] + lazy[ bel[y] ]);
		}
	}
}

int main()
{
	//freopen("in.txt","r",stdin);
	solve_fenkuai();
	return 0;
}

[

T2

在这里插入图片描述

#include<bits/stdc++.h>
#define LL long long
#define N 1000001
#define SQ 2010
using namespace std;

LL a[N],size[SQ],st[SQ],ed[SQ],lazy[SQ],bel[N],n,m,sq,l,r,w,c,ans,x,y;
vector<LL> v[SQ];

void init_block()
{
	sq = sqrt( n );
	for(int i=1;i<=sq;i++)
	{
		st[i] = n/sq * (i-1) + 1;
		ed[i] = n/sq * i; 
	}
	ed[sq] = n;
	for(int i=1;i<=sq;i++)
	{
		for(int j=st[i];j<=ed[i];j++)
		{
			bel[j] = i;
		}
		size[i] = ed[i] - st[i] + 1;
	}
}

void update(int p)
{
	v[p].clear();
	for(int i=0;i<=size[i];i++)//
	{
		v[p].push_back( a[ st[p] + i ] );
	}
	sort( v[p].begin() , v[p].end() ); 
}

void solve()
{
	cin>>n;
//	getchar();
	for(int i=1;i<=n;i++)
	{
		scanf("%lld",&a[i]);
	}
	init_block();
	for(int i=1;i<=sq;i++)
	{
		for(int j=st[i];j<=ed[i];j++)
		{
			v[i].push_back( a[j] );
		}
		sort( v[i].begin() , v[i].end() );
	}
	while( n-- )
	{
		LL op;
		scanf("%lld",&op);
		if( op==0 )
		{
			scanf("%lld %lld %lld",&x,&y,&w);
			if( bel[x]==bel[y] )
			{
				for(int i=x;i<=y;i++)
				{
					a[i] += w;
				}
				update( bel[x] );
				continue;
			}
			for(int i=x;i<=ed[ bel[x] ];i++)
			{
				a[i] += w;
			}
			update( bel[x] );
			for(int i=st[ bel[y] ];i<=y;i++)
			{
				a[i] += w;
			}
			update( bel[y] );
			for(int i=bel[x]+1;i<=bel[y]-1;i++)
			{
				lazy[i] += w;
			}
		}
		else
		{
			scanf("%lld %lld %lld",&x,&y,&c);
			c *= c;
			ans = 0;
			if( bel[x] == bel[y] )
			{
				for(int i=x;i<=y;i++)
				{
					if( a[i]+lazy[ bel[x] ] < c ) ans++;
				}
				printf("%lld\n",ans);
				continue;
			}
			
			for(int i=x;i<=ed[ bel[x] ];i++)
			{
				if( a[i]+lazy[ bel[x] ] < c ) ans++;
			}
			for(int i=st[ bel[y] ];i<=y;i++)
			{
				if( a[i]+lazy[ bel[y] ] < c ) ans++;
			}
			for(int i=bel[x]+1;i<=bel[y]-1;i++)
			{
				ans += size[i] - (v[i].end() - lower_bound( v[i].begin(), v[i].end(), c-lazy[i] ));
			}
			printf("%lld\n",ans);
		}
	}
}

int main()
{
	//freopen("in.txt","r",stdin);
	solve();
	return 0;
 } 

T7

很显然,如果只有区间乘法,和分块入门 1 的做法没有本质区别,但要思考如何同时维护两种标记。

我们让乘法标记的优先级高于加法(如果反过来的话,新的加法标记无法处理)

若当前的一个块乘以 m t a g 1 mtag1 mtag1(乘法标记)后加上 a t a g 1 atag1 atag1(加法标记),这时进行一个乘 m t a g 2 mtag2 mtag2的操作,则原来的标记变成 m t a g 1 ∗ m t a g 2 mtag1*mtag2 mtag1mtag2 a t a g 1 atag1 atag1* m t a g 2 mtag2 mtag2

若当前的一个块乘以 m t a g mtag mtag后加上a1,这时进行一个加a2的操作,则原来的标记变成 m t a g mtag mtag a t a g 1 + a t a g 2 atag1+atag2 atag1+atag2
ACcode:

#include<bits/stdc++.h>
#define LL long long 
#define N 110000
#define SQ 10100
using namespace std;
const int MOD = 10007;
int sq,n,t,a[N],mtag[SQ],atag[SQ],bl[N];

void reset(int id)//把标记放下放
{
	for(int i = (id-1)*sq+1;i<=min( id*sq , n );i++ )
	{
		a[i] = (a[i]*mtag[ id ]  + atag[ id ]) % MOD;
	}
	atag[id] = 0;
	mtag[id] = 1;
}

void solve(int op,int l,int r,int c)
{
	reset( bl[l] );
	for(int i=l;i<=min( bl[l]*sq , r );i++)
	{
		if( op==0 ) a[i] += c;
		else a[i] *= c;
		a[i] %= MOD;
	}
	if( bl[l] != bl[r] )
	{
		reset( bl[r] );
		for(int i=(bl[r]-1)*sq+1;i<=r;i++)
		{
			if( op==0 ) a[i] += c;
			else a[i] *= c;
			a[i] %= MOD;
		}
	}
	for(int i=bl[l]+1;i<=bl[r]-1;i++)
	{
		if( op==0 ) 
		{
			atag[i] = (atag[i]+c)%MOD;
		}
		else 
		{
			mtag[i] = (mtag[i]*c)%MOD;
			atag[i] = (atag[i]*c)%MOD;
		}
	}
}

int main()
{
	//freopen("in.txt","r",stdin);
	cin>>n;
	sq = sqrt(n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		bl[i] = (i-1)/sq + 1;
	}
	for(int i=1;i<=bl[n];i++) mtag[i] = 1;
	for(int i=1;i<=n;i++)
	{
		int op,l,r,c;
		scanf("%d %d %d %d",&op,&l,&r,&c);
		if( op==2 ) printf("%d\n",(a[r]*mtag[ bl[r] ] + atag[ bl[r] ])%MOD);
		else solve( op,l,r,c );
	}
	return 0;
} 

T8

在这里插入图片描述

#include<bits/stdc++.h>
#define int long long
#define LL long long
#define N 100100
#define SQ 110000
using namespace std;

int n,a[N],bl[N],sq;
int pd[SQ],b[SQ];
int st[SQ],ed[SQ];


void reset(int id)//下放标记
{
	if( b[id]==-1 ) return ;
	int rr = min( id*sq , n );
	for(int i=(id-1)*sq+1;i<=rr;i++
	{
		a[i] = b[id];
	}
	b[id] = -1;
}


int find1(int l,int r,int c)
{
	int sum = 0;
	reset( bl[l] );
	for(int i=l;i<=min( bl[l]*sq ,r );i++)
	{
		if( a[i]==c ) sum++;
		else a[i] = c;
	}
	if( bl[r]==bl[l] ) return sum;
	reset( bl[r] );
	for(int i=( bl[r]-1 )*sq + 1 ;i<=r;i++)
	{
		if( a[i]==c ) sum++;
		else a[i] = c;
	}
	for(int i=bl[l]+1;i<=bl[r]-1;i++)
	{
		if( b[i]!=-1 )
		{
			if( b[i]==c ) sum += sq;
			else b[i] = c;
		}
		else
		{
			int rr = min( i*sq , n );
			for(int j=(i-1)*sq+1;j<=rr;j++)
			{
				if( a[j]==c ) sum++;
				a[j] = c;
			}
			b[i] = c;
		}
	}
	return sum;
}

void solve()
{
	memset(b,-1,sizeof(b));
	scanf("%lld",&n);
	sq = sqrt(n);
	for(int i=1;i<=n;i++)
	{
		scanf("%lld",&a[i]);
		bl[i] = (i-1)/sq + 1;
	}
//	init();
	for(int i=1;i<=sq;i++)
	{
		st[i] = (i-1)*sq+1;
		ed[i] = i*sq;
	}
//	ed[sq] = n;
	for(int i=1;i<=n;i++)
	{
		int c,l,r;
		scanf("%lld %lld %lld",&l,&r,&c);
		printf("%lld\n", find1( l,r,c ) );
	}
}

signed main()
{
	//freopen("in.txt","r",stdin);
	solve();
	return 0;
}

T9

这题很有意思,我们要先发现一个性质,对于区间 [ l , r ] [l,r] [l,r]的众数一定满足下面三种情况:

  1. b l [ l ] bl[l] bl[l]这个块中 l l l之后的部分的众数
  2. b l [ r ] bl[r] bl[r]这个块中 r r r之前的部分的众数
  3. b l [ l ] + 1 bl[l]+1 bl[l]+1 b l [ r ] − 1 bl[r]-1 bl[r]1块的众数

上述第三个可以预处理出来,对于每次询问,直接枚举第一二种情况的数字即可。

#include<bits/stdc++.h>
#define LL long long 
#define PB push_back
#define N 101010
#define SQ 1251
using namespace std;

map<int,int> ma;
int n,a[N],f[SQ][SQ],cnt[N],bl[N],tot = 0,val[N],sq;
int st[SQ],ed[SQ];
vector<int> ve[N];//存储编号相同时为原来的第几个值,用于二分求众数
//利用ma离散化数据,f[i][j]记录从第i块到第j块内的最小众数,val记录真实大小,a离散化后存的是编号大小,st,ed分别存了一个块的开头结尾位置

void pre(int id)//预处理f[i][j]数组
{
	memset(cnt,0,sizeof(cnt));
	int maxx = 0,ans = 0;
	for(int i=st[id];i<=n;i++)//从当前块的开始位置一直扫扫到n
	{
		int v = bl[ i ];
		cnt[ a[i] ]++;
		if( cnt[ a[i] ] > maxx || ( cnt[ a[i] ]==maxx && val[ a[i] ] < val[ ans ] ) )
		{
			ans = a[i];
			maxx = cnt[ a[i] ];
		}
		f[id][v] = ans;
	}
}

int query2(int l,int r,int x)//二分求一个数字x在区间[l,r]出现的次数!!!!
{
	int v = upper_bound(ve[x].begin(),ve[x].end(),r)-lower_bound(ve[x].begin(),ve[x].end(),l);
	return v;
}

int query(int l,int r)
{
	int ans = f[ bl[l]+1 ][ bl[r]-1 ];//中间一整块的众数
	int maxx = query2(l,r,ans);
	for(int i=l;i<=min( r,ed[ bl[l] ] );i++)
	{
		int sum = query2( l,r,a[i] );
		if( sum>maxx || ( sum==maxx && val[ a[i] ]<val[ ans ] ) )
		{
			maxx = sum;
			ans = a[i];
		}
	}
	if( bl[l] != bl[r] )
	{
		for(int i=st[ bl[r] ];i<=r;i++)
		{
			int sum = query2(l,r,a[i]);
			if( sum>maxx || ( sum==maxx && val[ a[i] ] < val[ ans ] ) )
			{
				ans = a[i];
				maxx = sum;
			}
		}
	}
	return val[ ans ];
}

void solve()
{
	scanf("%d",&n);
	sq = 80;
	for(int i=1;i<=n;i++) 
	{
		scanf("%d",&a[i]);
		if( !ma[a[i]] )
		{
			ma[ a[i] ] = ++tot;
			val[tot] = a[i];
		}
		a[i] = ma[ a[i] ];
		ve[ a[i] ].PB( i );
		bl[i] = (i-1)/sq + 1;
	}
	for(int i=1;i<=bl[n];i++)
	{
		st[i] = (i-1)*sq + 1;
		ed[i] = i*sq;
	}
	ed[bl[n]] = n;
	for(int i=1;i<=bl[n];i++) pre(i);
	for(int i=1;i<=n;i++)
	{
		int l,r;
		scanf("%d %d",&l,&r);
		if( l>r ) swap(l,r);
		printf("%d\n",query(l,r));
	}
}

int main()
{
	//freopen("in.txt","r",stdin);
	solve();
	return 0;
}

T10 (分块)蒲公英

其实跟T9差不多…

#include<bits/stdc++.h>
#define LL long long
#define N 40100
#define SQ 500
#define PB push_back
using namespace std;


map<int,int> ma;
int st[SQ],ed[SQ],cnt[40010],bl[N],n,m,a[N],val[N],tot = 0,sq,f[501][501];
vector<int> ve[40010];

void init()
{
	val[0] = -1;
	for(int i=1;i<=n;i++)
	{
		bl[i] = (i-1)/sq + 1;
	}
	for(int i=1;i<=bl[n];i++)
	{
		st[i] = (i-1)*sq + 1;
		ed[i] = i*sq;
	}
	ed[ bl[n] ] = n;
}

void pre(int x)
{
	memset(cnt,0,sizeof(cnt));
	int v, maxx = 0, ans = 0;
	for(int i=st[x];i<=n;i++)
	{
		 v = bl[i];
		 cnt[ a[i] ]++;
		 if( cnt[ a[i] ]>maxx || ( cnt[ a[i] ]==maxx && val[ a[i] ]<val[ ans ] ) )
		 {
		 	maxx = cnt[ a[i] ];
		 	ans = a[i];
		 }
		 f[x][v] = ans;
	}
}


int query_cnt(int l,int r,int x)
{
	return 
	upper_bound( ve[x].begin(), ve[x].end(), r ) - lower_bound( ve[x].begin(), ve[x].end(), l );
}

int query(int l,int r)
{
	int ans= f[ bl[l]+1 ][ bl[r]-1 ],maxx = query_cnt( l,r,ans );
	for(int i=l;i<=min( r,ed[ bl[l] ] );i++)
	{
		int v = query_cnt( l,r,a[i] );
		if( v>maxx || (( v==maxx && val[ a[i] ]<val[ ans ] )) )
		{
			maxx = v;
			ans = a[i];
		}
	}
	if( bl[l] != bl[r] )
	{
		for(int i=st[ bl[r] ];i<=r;i++)
		{
			int v = query_cnt( l,r,a[i] );
			if( v>maxx || ( v==maxx && val[ a[i] ]<val[ans] ) )
			{
				maxx = v;
				ans = a[i];
			}
		}
	}
	return val[ans];
}

void solve()
{
	cin>>n>>m;
	sq = sqrt(n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		if( ma[ a[i] ]==0 )
		{
			ma[ a[i] ] = ++tot;
			val[tot] = a[i];
		}
		a[i] = ma[ a[i] ];
		ve[ a[i] ].PB( i );
	}
	init();
	int l,r,x = 0;
	for(int i=1;i<=bl[n];i++)
		pre(i);
	for(int i=1;i<=m;i++)
	{
		scanf("%d %d",&l,&r);
		l = (l+x-1)%n + 1;
		r = (r+x-1)%n + 1;
		if( l>r ) swap( l,r );
		x = query( l,r );
		printf("%d\n",x);
	}
}

int main()
{
	//freopen("in.txt","r",stdin);
	solve();	
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值