加载中…

返回上一页

分块和莫队

分块

什么是分块?

分块,就是把一个整体转化为多个离散的小块进行处理.

它的核心思想就是要把每次修改或询问拆成一个个小部分,两边进行暴力处理,中间的整块进行整块操作.

首先,如何分.

块长不宜太大也不宜太小,一般选取大小为 即可.

首先处理出一个 belong 数组,表示每一个位置属于哪一个块. 记录一个 sted 表示每一个块内的起始和末尾.

点击查看代码
len=sqrt(n);
for(rll i=1;i<=len;i++)
	st[i]=n/len*(i-1)+1,
	ed[i]=n/len*i;
ed[len]=n;
for(rll i=1;i<=len;i++)
	for(rll j=st[i];j<=ed[i];j++)
		bel[j]=i;

如何修改:

假设当前要修改的区间是 [l,r].

lr 处于同一块内,直接暴力扫即可.

若不处于同一块内,先把两边的单独部分暴力处理,然后对于整块的部分,直接加上块中的标记.

这样每次处理复杂度是 O(n) 的.

具体实现:

点击查看代码
inline void update(rll l,rll r,rll v)
{
	if(bel[l]==bel[r])
	{
		for(rll i=l;i<=r;i++) a[i]+=v,sum[bel[l]]+=v;
		return;
	}
	for(rll i=l;i<=ed[bel[l]];i++) a[i]+=v,sum[bel[l]]+=v;
	for(rll i=st[bel[r]];i<=r;i++) a[i]+=v,sum[bel[r]]+=v;
	for(rll i=bel[l]+1;i<bel[r];i++) mark[i]+=v;
}

查询的话,和修改大致是一样的. 也是单块/两头暴力扫,中间整块加.

具体实现:

点击查看代码
inline void query(rll l,rll r)
{
	rll ans=0;
	if(bel[l]==bel[r])
	{
		for(rll i=l;i<=r;i++) ans+=a[i]+mark[bel[l]];
		return ans;
	}
	for(rll i=l;i<=ed[bel[l]];i++) ans+=a[i]+mark[bel[l]];
	for(rll i=st[bel[r]];i<=r;i++) ans+=a[i]+mark[bel[r]];
	for(rll i=bel[l]+1;i<bel[r];i++) ans+=sum[i]+mark[i]*(ed[i]-st[i]+1);
	return ans;
}

莫队

也是一个解决区间查询问题的算法.

具体操作:

初始钦定一个区间 [l,r],对于每一次询问,将其拓展到 [l-1,r][l+1,r][l,r-1][l,r+1].

离线把每一个询问存起来,按照奇偶化排序,处理时将 lr 指针移动到每一个询问的两端点.

点击查看代码
sort(q+1,q+m+1);
for(rll i=1,l=1,r=0;i<=m;i++)
{
	while(l>q[i].l) upd(a[--l],1);
	while(r<q[i].r) upd(a[++r],1);
	while(l<q[i].l) upd(a[l++],-1);
	while(r>q[i].r) upd(a[r--],-1);
	ans[q[i].id]=ANS;
}

注意这个加减的先后.

奇偶化排序,就是按照修改区间 l 所属块 bel[l] 排序,如果相等就以 r 为关键字,如果 l 所在块编号为奇数,那么从小到大排序;否则从大到小排序.

点击查看代码
struct node
{
	ll l,r,id;
	inline friend bool operator<(rg node a,rg node b)
	{
		if(bel[a.l]^bel[b.l]) return a.l<b.l;
		if(bel[a.l]&1) return a.r<b.r; return a.r>b.r;
	}
}q[maxn];

带修莫队

加一个时间戳 t记录已进行几次修改,并记录距离本次查询最近的修改位置.

每次查询的时候,如果查询要求修改的更多,那就把这些(从上次查询到这次查询之间的更新)修改一下.

码先不打了.

回滚莫队

不是 noip 要求掌握的内容,暂时不写.

posted @ 2022-10-11 06:48  1Liu  阅读(21)  评论(0)    收藏  举报