首页 > 其他分享 >莫队

莫队

时间:2024-08-25 19:18:33浏览次数:7  
标签:idx 50010 int long times include 莫队

普通版

前言

莫队是由集训队大佬莫涛提出来的,在此再次膜拜大佬!

思想

普通莫队主要用于离线的区间查询操作,当然,也不是所有的都适用,当一个区间 \([l,r]\) 的答案可以用 \(O(1)\) 的时间转化成 \([l+1,r],[l-1,r],[l,r+1],[l,r-1]\) 的答案,我们就可以考虑使用莫队。

具体怎么做呢?其实就是将当前区间的答案通过一次一次移动 \(l,r\) 变成另一个区间的答案。

然后就没有了?

当然不是,这样很容易就被卡了,怎么卡呢,只需要将一次询问放在最前,下一次有放在最后,如此循环往复,这样我们每次就会移动 \(n\) 次,就足够卡爆这个没加任何优化的方法。

怎么优化呢?我们可以修改每个询问的顺序,尽量让两个询问之间移动次数最少,这里说一种咋看有点玄学的方法,我们把序列分成 \(\sqrt{n}\) 个块,如果两个点的左端点不在同一块,左端点小的放前面,如果左端点所在块相同,那就比较右端点,右端点小的排前面。

复杂度证明

可以证明,最坏复杂度为 \(O(n\sqrt{n})\)。我有一个绝妙的证明方法,可惜这里的空白太小了(分明是作者不会)。

优化

还能优化?没错,我们修改一下排序规则,如果两个点的左端点不在同一块,左端点小的放前面,如果左端点所在块相同,那就比较右端点,如果当前块编号为奇数,右端点小的放前面,否则右端点大的放前面。还想问为什么?应该不需要我放那句话了吧。

小B的询问

题面

小B 有一个长为 \(n\) 的整数序列 \(a\),值域为 \([1,k]\)。
他一共有 \(m\) 个询问,每个询问给定一个区间 \([l,r]\),求:

\[\sum\limits_{i=1}^k c_i^2 \]

其中 \(c_i\) 表示数字 \(i\) 在 \([l,r]\) 中的出现次数。
小B请你帮助他回答询问。

思路

莫队板子题。问题在于该如何修改呢?其实很简单,当我们将某个点加入当前区间时,设这个点所表示的值在当前区间的值为 \(x\),那么答案会增加 \((x+1)^2-x^2\),化简得 \(2x+1\),当我们将某个点从当前区间删除时,设这个点所表示的值在当前区间的值为 \(x\),那么答案会减少 \(x^2-(x-1)^2\),化简得 \(2x-1\)。

代码

#include<iostream>
#include<cmath>
#include<string>
#include<cstring>
#include<algorithm>
using namespace std;
int a[50010];
int b[50010];
int pos[50010];
long long ans[50010];
struct node{
	int l,r;
	int idx;
}c[50010];
long long cnt=0;
bool cmplr(node a,node b){
	if(pos[a.l]!=pos[b.l])return a.l<b.l;
	if(pos[a.l]&1)return a.r<b.r;
	return a.r>b.r;
}
void pplus(int x){
	cnt+=(b[x]*2)+1;
	b[x]++;
	return;
}
void perase(int x){
	cnt-=(b[x]*2)-1;
	b[x]--;
	return;
}
int main(){
    cin.tie(0);
	ios::sync_with_stdio(false);
	int n,m,k;
	cin>>n>>m>>k;
	int block=n/sqrt(m);
	for(int i=1;i<=n;i++){
		cin>>a[i];
		pos[i]=(i-1)/block+1;
	}
	for(int i=1;i<=m;i++){
		cin>>c[i].l>>c[i].r;
		c[i].idx=i;
	}
	sort(c+1,c+m+1,cmplr);
	int l=1,r=0;
	for(int i=1;i<=m;i++){
		while(l>c[i].l){
			l--;
			pplus(a[l]);
		}
		while(r<c[i].r){
			r++;
			pplus(a[r]);
		}
		while(l<c[i].l){
			perase(a[l]);
			l++;
		}
		while(r>c[i].r){
			perase(a[r]);
			r--;
		}
		ans[c[i].idx]=cnt;
	}
	for(int i=1;i<=m;i++){
		cout<<ans[i]<<"\n";
	}
	cout<<flush;
    return 0;
}

小Z的袜子

题面

作为一个生活散漫的人,小 Z 每天早上都要耗费很久从一堆五颜六色的袜子中找出一双来穿。终于有一天,小 Z 再也无法忍受这恼人的找袜子过程,于是他决定听天由命……

具体来说,小 Z 把这 \(N\) 只袜子从 \(1\) 到 \(N\) 编号,然后从编号 \(L\) 到 \(R\) 的袜子中随机选出两只来穿。尽管小 Z 并不在意两只袜子是不是完整的一双,他却很在意袜子的颜色,毕竟穿两只不同色的袜子会很尴尬。

你的任务便是告诉小 Z,他有多大的概率抽到两只颜色相同的袜子。当然,小 Z 希望这个概率尽量高,所以他可能会询问多个 \((L,R)\) 以方便自己选择。

思路

其实没那么难,设当前区间编号为 \(i\) 的袜子有 \(x\) 双,根据概率论和一点数学,答案是:

\[\displaystyle\frac{c_1\times (c_1-1)/2+c_2\times (c_2-1)/2+\dots+c_n\times (c_n-1)/2)}{(R-L+1)\times(R-L)/2} \]

\[\displaystyle=\frac{c_1\times (c_1-1)+c_2\times (c_2-1)+\dots+c_n\times (c_n-1))}{(R-L+1)\times(R-L)} \]

\[=\displaystyle\frac{{c_1}^2+{c_2}^2+\dots+{c_n}^2-(c_1+c_2+\dots+c_n)}{(R-L+1)\times(R-L)} \]

\[=\displaystyle\frac{{c_1}^2+{c_2}^2+\dots+{c_n}^2-(R-L+1)}{(R-L+1)\times(R-L)} \]

现在会做了吧,就是上一题加一点东西。

代码

#include<iostream>
#include<cmath>
#include<string>
#include<cstring>
#include<algorithm>
using namespace std;
int a[50010];
int b[50010];
int pos[50010];
long long aa[50010],bb[50010];
struct node{
	int l,r;
	int idx;
}c[50010];
long long cnt=0;
bool cmplr(node a,node b){
	if(pos[a.l]!=pos[b.l])return a.l<b.l;
	if(pos[a.l]&1)return a.r<b.r;
	return a.r>b.r;
}
void pplus(int x){
	cnt+=(b[x]*2)+1;
	b[x]++;
	return;
}
void perase(int x){
	cnt-=(b[x]*2)-1;
	b[x]--;
	return;
}
int pgcd(long long a,long b){
	return b?pgcd(b,a%b):a;
}
int main(){
    cin.tie(0);
	ios::sync_with_stdio(false);
	int n,m;
	cin>>n>>m;
	int block=n/sqrt(m);
	for(int i=1;i<=n;i++){
		cin>>a[i];
		pos[i]=(i-1)/block+1;
	}
	for(int i=1;i<=m;i++){
		cin>>c[i].l>>c[i].r;
		c[i].idx=i;
	}
	sort(c+1,c+m+1,cmplr);
	long long l=1,r=0;
	for(int i=1;i<=m;i++){
		while(l>c[i].l){
			l--;
			pplus(a[l]);
		}
		while(r<c[i].r){
			r++;
			pplus(a[r]);
		}
		while(l<c[i].l){
			perase(a[l]);
			l++;
		}
		while(r>c[i].r){
			perase(a[r]);
			r--;
		}
		if(l==r){
			aa[c[i].idx]=0;
			bb[c[i].idx]=1;
		}else{
			aa[c[i].idx]=cnt-(r-l+1);
			bb[c[i].idx]=(r-l+1)*(r-l);
			int t=pgcd(aa[c[i].idx],bb[c[i].idx]);
			aa[c[i].idx]/=t;
			bb[c[i].idx]/=t;
		}
	}
	for(int i=1;i<=m;i++){
		cout<<aa[i]<<"/"<<bb[i]<<"\n";
	}
	cout<<flush;
    return 0;
}

标签:idx,50010,int,long,times,include,莫队
From: https://www.cnblogs.com/pengdave/p/18379354

相关文章

  • 莫队算法C/C++实现
    目录简介 算法原理算法步骤C++实现应用场景莫队算法(Mo'sAlgorithm)是一种用于解决区间查询和更新问题的算法,由俄罗斯选手莫洛佐夫(MoMorozov)提出。它在算法竞赛和某些计算密集型任务中非常有用,尤其是在需要处理大量区间查询和更新操作时。莫队算法以其高效性和简洁性......
  • 莫队详解
    莫队详解一、莫队定义莫队是由2010年信息学国家集训队队员莫涛发明的一种算法,可以将静态离线区间查询的时间复杂度将至\(O(m\sqrt{n})\)下面便是一道莫队例题Lougu1972[SDOI2009]HH的项链虽然这道题莫队过不了,但是确实是很好的一道莫队题。题意:给你一个又\(n\)个......
  • 分块、莫队、块状链表及一些根号方法 总结
    第二分块没改出来给我干破防了。提交记录:五彩斑斓的世界()。分块的种类序列分块:本质是在下标轴(\(i\))上分块。值域分块:本质是在值的轴(\(a_i\))上分块。操作分块:本质是在时间轴(一般是输入顺序)上分块。这几种分块应该是可以套的。分块、莫队、根号分治的核心平衡。平衡整块......
  • 莫队的 1.5 近似构造 题解
    前言题目链接:洛谷。感觉T4比T3水,虽然我都没做出来。题意简述给定\(1\simn\)的排列\(a\)和\(m\)个区间\([l_i,r_i]\)。定义值域区间\([L,R]\)的价值为\(\operatorname{val}([L,R])\operatorname{:=}\max\limits_{i=1}^m\sum\limits_{k=l_i}^{r_i}[L......
  • 莫队算法
    前言莫队是由莫涛提出的一种离线算法,是分块与双指针的结合,一般可以在\(O(n\sqrtn)\)或者\(O(n\sqrtm)\)的复杂度解决一些种类问题。普通莫队SP3267DQUERY-D-query给你一个长度为\(n\)的数列\(A\),\(m\)次询问,每次询问区间\([l,r]\)内的不同数字的个数。如果......
  • 分块&莫队
    分块这是一种思想,不是一种数据结构。学校题单里的题大都是用这种思想做的。分块就是将一个序列分成多个不相交的区间,称为块。理想块长是\(\sqrt{n}\),至于为什么,它是由时间复杂度\(O(s+\frac{n}{s})(s\)为块长\()\)通过均值不等式算出来的。。。定义\(pos[i]\):表示\(i\)......
  • 分块 and 莫队
    分块一种暴力的数据结构,十分朴素的做法。能够处理许多问题。基础分块例\(1\):P3372【模板】线段树1经典老题,这次使用分块做法。我们将整个序列分为若干大小为\(T\)的块,记录其块的和和懒标记,对\([l,r]\)进行操作时,设左边界\(l\)位与块\(q\),左边界\(r\)位与块\(p\)......
  • 莫队算法
    莫队是一种优美的暴力,分为普通莫队、树上莫队、带修莫队、回滚莫队等,但是这个人太菜了导致只会普通莫队。首先,%%%莫涛大神。传奇人物就直接放图吧:stostostostostostostostostostostostostostostoMoTaoorzorzorzorzorzorzorzorzorzorzorzorzorzo......
  • 回滚莫队
    前置知识普通莫队当普通莫队做一些删除操作或增加操作非常困难时,回滚莫队便腾空出现,解救苍生针对困难的操作,分为两种回滚莫队,一个是不删除莫队,一个是不增加莫队。核心思路都是一样的:既然只能实现一种操作,那么就只用一种操作,剩下的交给回滚解决。什么是回滚回滚(Rollbac......
  • 离线算法 莫队算法进阶
    前算是把之前的坑填一填吧。这篇文章主要包含带修莫队,二维莫队等莫队算法的进阶应用,观看前请确保您已经熟练掌握了基本的莫队算法,不会的可以戳这里。带修莫队众所周知,普通莫队是不支持修改的,因为我们为了得到更优的时间复杂度,需要将每次询问离线下来,打乱顺序。不过我们也......