luogu P2709 小B的询问

题面传送门
莫队,由原国家集训队队员莫涛同学提出的一种优雅的暴力,在分块的基础上以 O ( n ) O(\sqrt{n}) O(n )的均摊复杂度维护大量的区间信息(这里的大量,意为每个节点有很多信息,并非多次询问)。因其思路清晰,代码简单,变化多样,维护区间信息多样化,成为数据结构题的骗分神器,也成为了各大数据结构比赛的一大考点
莫队在分块的思维上用暴力的手段维护区间信息。
例如本题,暴力用 O ( n ) O(n) O(n)的复杂度循环枚举答案。
我们可以换个思路暴力,设上一个的区间为 l 1 , r 1 l_1,r_1 l1r1,设现在要维护的区间为 l 2 , r 2 l_2,r_2 l2,r2,则让 l 1 l_1 l1 l 2 l_2 l2移, r 1 r_1 r1 r 2 r_2 r2移,在移的过程中用上一次的答案以及每移一步更新的答案生成出新的答案,时间复杂度 O ( n ) O(n) O(n)
很明显,这种方法的时间复杂度与区间之间的相对位置有关,而我们可以人为地改变这个顺序,所以说莫队是一种离线算法。
那排序的那个 c m p cmp cmp函数里面写什么就非常重要了,尝试一下,按左端点排序?那总共左端点的时间复杂度为 O ( n ) O(n) O(n),而右端点可以达到 O ( n m ) O(nm) O(nm),用右端点排序也是一样。我们要有一个方法让左端点和右端点距离下一个端点的最大值尽可能地最小
此时莫队的精髓就在里面了,莫队算法把整个数列分为 k k k块,左端点如果不在一个块内按左端点所在块排,左端点如果在同一个块里按右端点排序
我们可以来计算一下这样的话的时间复杂度是多少,总共有 k k k块,对于每一块,一次移动左端点最多移动 n k \frac{n}{k} kn次,那么全部算下来左端点最多移动 n m k \frac{nm}{k} knm次,右端点整块最多移动 n n n次,全部下来右端点最多移动 k n kn kn次,时间复杂度为 O ( n m k + k n ) O(\frac{nm}{k}+kn) O(knm+kn),此时 k k k n \sqrt{n} n 为最小值,全部复杂度为 O ( ( n + m ) n ) O((n+m)\sqrt{n}) O((n+m)n )
说到底,莫队算法和其他维护区间信息的题目一样,难就难在如何维护区间信息,例如这一题,当 c i + 1 c_i+1 ci+1时,答案怎么维护?很明显是加上 2 c i + 1 2c_i+1 2ci+1,减也是减去 2 c i − 1 2c_i-1 2ci1
代码实现:

#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
int n,m,k,ks,f[100039],s[100039],q[100039],tot,pus,a[100039],l,r,ans[100039];
struct yyy{
	int x,y,num;
}sf[100039];
inline bool cmp(yyy x,yyy y){
	if(s[x.x]==s[y.x]) return x.y<y.y;
	return x.x<y.x;
}
int main(){
	register int i,j;
	scanf("%d%d%d",&n,&m,&k);
	ks=sqrt(n);
	for(i=1;i<=n;i++)scanf("%d",&a[i]);
	for(i=1;i<=ks;i++) s[i]=1;
	for(i=ks+1;i<=n;i++) s[i]=s[i-ks]+1;
	for(i=1;i<=m;i++){
		scanf("%d%d",&sf[i].x,&sf[i].y);
		sf[i].num=i;
	}
	sort(sf+1,sf+m+1,cmp);
	l=sf[1].x;
	r=sf[1].y;
	for(i=sf[1].x;i<=sf[1].y;i++){
		tot+=f[a[i]]*2+1;
		f[a[i]]++;
	}
	ans[sf[1].num]=tot;
	for(i=2;i<=m;i++){
		while(sf[i].x<l) l--,tot+=f[a[l]]*2+1,f[a[l]]++;
		while(sf[i].x>l) tot-=f[a[l]]*2-1,f[a[l]]--,l++;
		while(sf[i].y<r) tot-=f[a[r]]*2-1,f[a[r]]--,r--;
		while(sf[i].y>r) r++,tot+=f[a[r]]*2+1,f[a[r]]++;
		ans[sf[i].num]=tot;
	}
	for(i=1;i<=m;i++) printf("%d\n",ans[i]);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值