莫队--优雅的暴力

莫队、带修莫队、回滚莫队、树上莫队
摘要由CSDN通过智能技术生成

当遇到大量的区间询问时,假如区间的左右下标有着一定的规律,我们可以如何求解?

如:

[ 1 , 3 ] , [ 1 , 4 ] , [ 1 , 5 ] , [ 2 , 5 ] [1,3],[1,4],[1,5],[2,5] [1,3],[1,4],[1,5],[2,5]

当然是双指针!其时间复杂度从 n 2 n^2 n2下降到 o ( n ) o(n) o(n)

莫队就是这样一个算法,通过预处理询问顺序,来降低时间复杂度,当然,前提是能够预处理,强制在线的题目便与莫队无缘。

其预处理流程如下:

  • 首先将数据分块,分块大小为size
  • 将区间排序,如果区间的左端点落在同一个块中,那么我们将其按右端点大小排序。
  • 如果它们的左端点不在同一块中,那么便按照左端点升序排序。

当我们处理完区间顺序之后,剩下的就是之后双指针的左右移动罢了,考虑增加和删除对与答案的影响,也就结束了。

ll sum = 0;
	s[a[1]]++;
	for (int i = 1; i <= m; i++)
	{
   
		while (l < q[i].l)sum += del(l++);
		while (l > q[i].l)sum += add(--l);
		while (r < q[i].r)sum += add(++r);
		while (r > q[i].r)sum += del(r--);
		ans[q[i].id] = sum;
	}

那么问题来了,如何分析莫队算法的时间复杂度呢?

考虑对单一块进行询问,我们考虑最糟糕的情况,同一块中的左端点再反复横跳,先是再块的最左端,然后跑到最右端如此反复,当然,右端点是有序的,它只会后移操作。那么其实时间复杂度为 o ( s i z e ∗ m i + n ) o(\sqrt{size}*m_i+n) o(size mi+n),其中 m i m_i mi表示再第i块中的区间数.

那么对于总体的时间复杂度为 o ( s i z e ∗ m + n ∗ ( n s i z e ) ) o(\sqrt{size}*m+n*(\frac{n}{size})) o(size m+n(sizen))​。

洛谷P1494

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 5e4 + 5;
struct node {
   
	int l, r, id;
}q[maxn];
ll n, m, a[maxn], ans[maxn], l = 1, r = 1, sum, s[maxn], id[maxn], Size;
ll lef[maxn], righ[maxn];
bool cmp(struct node x, struct node y)
{
   
	if (id[x.l] == id[y.l])
	{
   
		if(id[x.l]&1)return x.r < y.r;
		return x.r>y.r;
	}//排序,左端点在同一块内,按右区间升序排序 ,奇数偶数优化
	return x.l < y.l;//否则,按左区间排序 
}
ll add(int x)
{
   
	ll gs = ++s[a[x]];
	return s[a[x]] * (s[a[x]] - 1) / 2 - (s[a[x]] - 1)*(s[a[x]] - 2) / 2;//返回影响
}
ll del(int x)
{
   
	ll gs = --s[a[x]];
	return gs * (gs - 1) / 2 - gs * (gs + 1) / 2;//返回影响
}
ll gcd(ll a, ll b)
{
   
	if (a == 0)return b>0?b:1;
	return b == 0 ? a : gcd(b, a%b);
}
int main()
{
   
	ios::sync_with_stdio(false);
	//freopen("P1494_1.in", "r+", stdin);
	cin >> n >> m;
	Size = n / sqrt(m * 2 / 3);//分块大小,会影响时间复杂度。
	for (int i = 1; i <= n; i++)
	{
   
		cin >> a[i];
		id[i] = (i - 1) / Size + 1;
	}
	for (int i = 1; i <= m; i++)
	{
   
		cin >> q[i].l >> q[i].r;
		q[i].id = i;
		lef[i] = q[i].l;
		righ[i] = q[i].r;
	}
	sort(q + 1, q + m + 1, cmp);
	ll sum = 0;
	s[a[1]]++;
	for (int i = 1; i <= m; i++)
	{
   
		while (l < q[i].l)sum += del(l++);
		while 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值