首页 > 其他分享 >分块&莫队

分块&莫队

时间:2024-08-19 16:28:15浏览次数:11  
标签:分块 int pos qnum maxn ans now 莫队

分块

这是一种思想,不是一种数据结构。学校题单里的题大都是用这种思想做的。

分块就是将一个序列分成多个不相交的区间,称为块。

理想块长是 \(\sqrt{n}\) ,至于为什么,它是由时间复杂度 \(O(s+\frac{n}{s})(s\)为块长\()\) 通过均值不等式算出来的。。。

定义

\(pos[i]\):表示 \(i\) 所属的块。

\(st[i]\):表示 \(pos[i]\) 的起始位置。

\(ed[i]\):表示 \(pos[i]\) 的终止位置

\(sum[i]\):表示第 \(i\) 块的总和。

点击查看代码

void build()
{
	len=sqrt(n);
	t=n/len;
	if(n%len) t++;
	for(int i=1;i<=t;i++)
	{
		st[i]=(i-1)*len+1;
		ed[i]=i*len;
	}
	ed[t]=n;
	for(int i=1;i<=n;i++)
	{
		pos[i]=(i-1)/len+1;
		sum[pos[i]]+=a[i];
	}
}

单点修改

直接修改即可,注意将 \(sum\) 数组也要更新。

区间修改

\(add[i]\):表示第 \(i\) 块加的值。

  • 在同一块:直接暴力修改。
  • 不在同一块:零散的块暴力修改,整块修改 \(add\) 数组的值。
点击查看代码
void update(int l,int r,int k)
{
	int sid=pos[l], eid=pos[r];
	if(sid==eid)//在同一块 
	{
		for(int i=l;i<=r;i++)
		{
			a[i]+=k;
		}
		sum[sid]+=(r-l+1)*k;
	}
	else
	{
		for(int i=sid+1;i<eid;i++)
		{
			add[i]+=k;
		}
		for(int i=sid;i<=ed[sid];i++)
		{
			a[i]+=k;
		}
		sum[sid]+=k*(ed[sid]-l+1);
		for(int i=st[eid];i<=r;i++)
		{
			a[i]+=k;
		}
		sum[eid]+=k*(r-st[eid]+1); 
	}
}

区间求和

点击查看代码
int query(int l,int r)
{
	int ans=0;
	int sid=pos[l], eid=pos[r];
	if(sid==eid)//在同一块 
	{
		for(int i=l;i<=r;i++)
		{
			ans+=a[i];
		}
	}
	else
	{
		for(int i=l;i<=ed[sid];i++)
		{
			ans+=a[i];
		}
		for(int i=st[eid];i<=r;i++)
		{
			ans+=a[i];
		}
		for(int i=sid+1;i<eid;i++)
		{
			ans+=sum[i];
		}
	}
	return ans;
}

区间求大于/小于/大于等于/小于等于

将每个块排序,使用二分/ \(lower\_bound\) 查询即可。

注:\(lower\_bound\) 返回的是这个序列里第一个大于等于查找的数

点击查看代码
//区间修改,区间查询大于等于c的个数
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+5;
int n, q, a[maxn]; 
int st[maxn], ed[maxn], pos[maxn];
int add[maxn], len;
vector<int> v[maxn];
void reset(int x)
{
	v[x].clear();
	for(int i=st[x];i<=ed[x];i++)
	{
		v[x].push_back(a[i]);
	}
	sort(v[x].begin(), v[x].end());
}
void build()
{
	len=sqrt(n);
	int t=n/len;
	if(n%len) t++;
	for(int i=1;i<=t;i++)
	{
		st[i]=(i-1)*len+1;
		ed[i]=i*len;
	}
	ed[t]=n;
	for(int i=1;i<=n;i++)
	{
		pos[i]=(i-1)/len+1;
		v[pos[i]].push_back(a[i]);
	}
	for(int i=1;i<=t;i++)
	{
		sort(v[i].begin(),v[i].end());
	}
}
void update(int l,int r,int v)
{
	int sid=pos[l], eid=pos[r];
	if(sid==eid)
	{
		for(int i=l;i<=r;i++)
		{
			a[i]+=v;
		}
		reset(sid);
	}
	else
	{
		for(int i=l;i<=ed[sid];i++)
		{
			a[i]+=v;
		}
		reset(sid);
		for(int i=sid+1;i<=eid-1;i++)
		{
			add[i]+=v;
		}
		for(int i=st[eid];i<=r;i++)
		{
			a[i]+=v;
		}
		reset(eid);
	}
}
int ask(int l,int r,int c)
{
	int sid=pos[l], eid=pos[r];
	int ans=0;
	if(sid==eid)
	{
		for(int i=l;i<=r;i++)
		{
			if(a[i]+add[sid]>=c) ans++;	
		}
		return ans;
	}
	else 
	{
		for(int i=l;i<=ed[sid];i++)
		{
			if(a[i]+add[sid]>=c) ans++;
		}
		for(int i=sid+1;i<=eid-1;i++)
		{
			int x=lower_bound(v[i].begin(), v[i].end(), c-add[i])-v[i].begin();
			ans+=(len-x);	
		}
		for(int i=st[eid];i<=r;i++)
		{
			if(a[i]+add[eid]>=c) ans++;
		}	
		return ans;	
	}
}
int main()
{
	cin>>n>>q;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
	}
	build();
	while(q--)
	{
		char opt;
		int l, r, c;
		cin>>opt>>l>>r>>c;
		if(opt=='M')
			update(l, r, c);
		else 
			cout<<ask(l, r, c)<<endl;
	}
	return 0;	
} 
根据情况修改ask的写法。

会发现用分块维护一个序列要考虑三个要素:

  1. 不完整的块怎么处理?
  2. 完整的块怎么处理?
  3. 要预处理什么信息?

求值函数怎么写?

  1. 端点在同一块:一般都是暴力处理。
  2. 端点不在同一块:分为角块和整块处理。

例题:


莫队

莫队是一种离线算法。且基于分块思想(一定不要忘了,否则会T的很惨)。一般不带修改,或有简单修改。

普通莫队

求区间数值种类数,暴力移动 \(l,r\) 点,暴力修改即可。根据题目调整 add 和 del 函数。

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=2e5+5;
int n, m, qq, a[maxn], p[maxn], ans[maxn], t[maxn], res;
struct node{
	int l, r, id;
}q[maxn];
int pos[maxn];
void build()
{
	int len=sqrt(n);
	for(int i=1;i<=n;i++)
	{
		pos[i]=(i-1)/len+1;
	}
}
bool cmp(node a,node b)
{
	if(pos[a.l]!=pos[b.l]) return pos[a.l]<pos[b.l];
	return a.r<b.r;
}
void add(int val)
{
	if(++t[val]==1) res++;
}
void del(int val)
{
	if(--t[val]==0) res--;
}
signed main()
{
	cin>>n>>qq;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
	}
	build();
	for(int i=1;i<=qq;i++)
	{
		int l, r;
		cin>>l>>r;
		q[i].id=i;
		q[i].l=l;
		q[i].r=r;
	}
	sort(q+1, q+qq+1, cmp);
	int l=0, r=0;
	for(int i=1;i<=qq;i++)
	{
		while(l>q[i].l) add(a[--l]);
		while(r<q[i].r) add(a[++r]);
		while(l<q[i].l) del(a[l++]);
		while(r>q[i].r) del(a[r--]);
		ans[q[i].id]=res;
	}
	for(int i=1;i<=qq;i++)
	{
		cout<<ans[i]<<endl;
	}
	return 0;
}

带修莫队

名字听着挺高级,但实际上就是在普通莫队上加入一些简单的修改。

带修莫队相比普通莫队多维护了一个时间戳,记录这是第几次修改。它的维护方式和 \(l,r\) 一样,都是将多的改回来,将少的补回去。需要注意的是 \(change\) 函数:

void change(int now,int i)
{
	if(c[now].pos>=q[i].l&&c[now].pos<=q[i].r)//在区间内 
	{
		if((++tmp[c[now].val])==1) res++;//将这个值改变,就多了一个新值,判断这个新值是否出现过 
		if((--tmp[a[c[now].pos]])==0) res--;////将这个值改变,就多了一个原来的值,判断这个原来的值是否还有
	}
	swap(c[now].val, a[c[now].pos]);//交换,便于后期使用(这里要仔细想想) 
}

P1903 [国家集训队] 数颜色 / 维护队列

注意块长是 \(n^{\frac{2}{3}}\)。

点击查看代码
//带修莫队 
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+5;
struct node{
	int l, r, time, id;
}q[maxn];
struct NODE{
	int pos, val;
}c[maxn];
int a[maxn], ans[maxn], tmp[maxn];
int n, m, k, res;
int st[maxn], ed[maxn], pos[maxn];
int len, qnum, tim;//qnum为询问的次数,tim为修改的次数 
void build()
{
	len=int(pow(n, 0.66));
	for(int i=1;i<=n;i++)
	{
		pos[i]=(i-1)/len+1;
	}
} 
void del(int v)
{
	if((--tmp[v])==0) res--; 
}
void add(int v)
{
	if((++tmp[v])==1) res++;
}
void change(int now,int i)
{
	if(c[now].pos>=q[i].l&&c[now].pos<=q[i].r)//在区间内 
	{
		if((++tmp[c[now].val])==1) res++;
		if((--tmp[a[c[now].pos]])==0) res--;
	}
	swap(c[now].val, a[c[now].pos]);//交换,便于后期使用 
}
bool cmp(node x,node y)
{
	if(pos[x.l]!=pos[y.l])
	{
		return pos[x.l]<pos[y.l];
	}
	if(pos[x.r]!=pos[y.r])
	{
		return pos[x.r]<pos[y.r];
	}
	if(x.time!=y.time)
	{
		return x.time<y.time;
	}
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
	}
	build();
	for(int i=1;i<=m;i++)
	{
		char opt;
		int x, y;
		cin>>opt>>x>>y;
		if(opt=='Q')
		{
			q[++qnum].l=x;
			q[qnum].r=y;
			q[qnum].time=tim;
			q[qnum].id=qnum;//注意这里是qnum		
		} 
		else
		{
			c[++tim].pos=x;
			c[tim].val=y; 
		}		
	}
	sort(q+1, q+qnum+1, cmp);
	int l=1, r=0, now=0;
	for(int i=1;i<=qnum;i++)
	{
		while(l>q[i].l) add(a[--l]);
		while(r<q[i].r) add(a[++r]);
		while(l<q[i].l) del(a[l++]);
		while(r>q[i].r) del(a[r--]);
		while(now>q[i].time) change(now--, i);
		while(now<q[i].time) change(++now, i);
		ans[q[i].id]=res;	
	}
	for(int i=1;i<=qnum;i++)
	{
		cout<<ans[i]<<endl;
	}
	return 0;
}
温馨提示:莫队无输出多半是板写错了,务必仔细检查。

标签:分块,int,pos,qnum,maxn,ans,now,莫队
From: https://www.cnblogs.com/zhouyiran2011/p/18367561

相关文章

  • 【数论】数论分块
    2024-18-19·最后更新时间:2024-18-19\(\Large\mathcal{1,solution(1)}\)如果我们把数\(n\)与小于等于\(n\)的数\(i\)的对应关系打印在表格上会是这样。\(i\)12345678910\(\Large\lfloor\frac{n}{i}\rfloor\)10532211111可以发现\(\Lar......
  • 分块 and 莫队
    分块一种暴力的数据结构,十分朴素的做法。能够处理许多问题。基础分块例\(1\):P3372【模板】线段树1经典老题,这次使用分块做法。我们将整个序列分为若干大小为\(T\)的块,记录其块的和和懒标记,对\([l,r]\)进行操作时,设左边界\(l\)位与块\(q\),左边界\(r\)位与块\(p\)......
  • 莫队算法
    莫队是一种优美的暴力,分为普通莫队、树上莫队、带修莫队、回滚莫队等,但是这个人太菜了导致只会普通莫队。首先,%%%莫涛大神。传奇人物就直接放图吧:stostostostostostostostostostostostostostostoMoTaoorzorzorzorzorzorzorzorzorzorzorzorzorzo......
  • 回滚莫队
    前置知识普通莫队当普通莫队做一些删除操作或增加操作非常困难时,回滚莫队便腾空出现,解救苍生针对困难的操作,分为两种回滚莫队,一个是不删除莫队,一个是不增加莫队。核心思路都是一样的:既然只能实现一种操作,那么就只用一种操作,剩下的交给回滚解决。什么是回滚回滚(Rollbac......
  • 离线算法 莫队算法进阶
    前算是把之前的坑填一填吧。这篇文章主要包含带修莫队,二维莫队等莫队算法的进阶应用,观看前请确保您已经熟练掌握了基本的莫队算法,不会的可以戳这里。带修莫队众所周知,普通莫队是不支持修改的,因为我们为了得到更优的时间复杂度,需要将每次询问离线下来,打乱顺序。不过我们也......
  • 根号分治莫队
    莫队参考文章:莫队细讲——从零开始学莫队莫队算法——从入门到黑题oiwiki--普通莫队莫队简介莫队算法是由莫涛提出的算法。在莫涛提出莫队算法之前,莫队算法已经Codeforces的高手圈里小范围流传,但是莫涛是第一个对莫队算法进行详细归纳总结的人。莫涛提出莫队算法时,只分析......
  • 线性结构查找(顺序、折半、分块)
    对于线性结构的查找,应掌握其查找的过程、构造判定树、分析平均查找长度等。一、顺序查找顺序查找又称线性查找,它对顺序表和链表都是适用的。对于顺序表,可通过数组下标递增来顺序扫描每个元素;对于链表,可通过指针next来依次扫描每个元素。顺序查找通常分为对一般的无......
  • 黑马Java零基础视频教程精华部分_15_基本查找/顺序查找、二分查找/折半查找、插值查找
    系列文章目录文章目录系列文章目录一、基本查找/顺序查找核心思想:从0索引开始挨个往后查找代码:练习:定义一个方法利用基本查找,查询某个元素在数组中的索引,数组包含重复数据。二、二分查找/折半查找核心思想:属于有序查找算法。用给定值先与中间结点比较,每次排除一半的......
  • 数据结构 分块 & 莫队
    分块一种优化暴力的思想。通常是将原数据划分成适当块(一般为\(\sqrt{n}\)),对每块数据进行预处理,进而达到比暴力更优的时间复杂度。划分确定块长后,一般需要开两个数组存储每一块的右边界与原数据所属块序号,更加方便后续操作。intsq=sqrt(n);for(inti=1;i<=sq;i++)ed[i]=n/......
  • 分块
    写在前面非常简单的分块,使我开心的转圈,稍微看了一下oiwiki就懂了,妈妈再也不用担心我的暴力了正文分块,非常优雅的暴力,本质是上是通过对原数据的适当划分,并在划分后的每一个块上预处理部分信息,从而较一般的暴力算法取得更优的时间复杂度。例如(博客萌新好不容易整的):\[\begin{......