莫队算法

参考讲解
莫队算法将暴力与分块两个算法合二为一,可以称之为优雅的暴力
莫队是一个必须离线的算法
本质是通过改变查询的顺序来实现降低时间复杂度

样例:求一个区间中每个数出现次数的平方和(多次询问)

我们可以用暴力来做每次枚举L~R,这样的暴力注定不可,我们考虑先一个等级的暴力
一开始指针区间为0->0每一个查询,左指针left和右指针right不断更新
比如Left=2,Right=4,
而题目要求查询L=1,r=5
left就要向前移动一位,也就是left–,而left位置上(也就是L=1这个位置)出现的数的次数自然就要+1(因为原本不在区间内,而现在在了)
同理,right要向后移动一位

添加和删除代码

inline void del(int x)
{
	sum[c[x]]--;
	ans-=2*sum[c[x]]+1;
}

inline void add(int x)
{
	sum[c[x]]++;
	ans+=2*sum[c[x]]-1;
}

指针移动代码

	for(long long i=1;i<=m;i++)
	{
		long long ql=q[i].l,qr=q[i].r;
		while(l<ql)
		{
			del(l++);
			//l++; 
		}
		while(l>ql)
		{
		//	l--;
			add(--l);
		}
		while(r<qr)
		{
		//	r++;
			add(++r);
		}
		while(r>qr)	
		{
			del(r--);
		//	r--;
		}
		anss[q[i].num]=ans;
	}

我们来分析一下时间复杂度,每一次询问移动两最大都会是O(N)
总复杂度为O(N*M)
貌似这样并不是很优化
接下来介绍莫队的核心
在本题中询问是多次的
我们按照询问给定的区间,然后对询问进行分块
比如九组询问:
2 3
1 4
4 5
1 6
7 9
8 9
5 8
6 8
n=9,我们可以分成三块,
也就是1 ~ 3 , 4 ~ 6,7 ~ 9
我们以L为准来将询问划分到不同块
在每一个单独的块内,我们让R更小的排在前面
对上面的九组询问可得:
(2,3)/(1,4)/(1,6)
(4,5)/(5,8)/(6,8)
(7,9)/(8,9)

实现代码:

bool cmp(node a,node b)
{
	return (a.r/block)==(b.r/block)?a.l<b.l:a.r<b.r;
 } 
/*
//根据奇偶性排序 
bool cmp(node a,node b)
{
	return pos[a.l]^pos[b.l]?pos[a.l]<pos[b.l]:pos[a.l]&1?a.r<b.r:a.r>b.r;
 } 
 */

此时时间复杂度为O((N+M)*√N)
分析过程:
我将不大明白。。。所以直接引用参考题解
在这里插入图片描述

代码


#include<bits/stdc++.h>
using namespace std;

const int maxn=50005;
long long int c[maxn];
long long int sum[maxn];

struct node{
	long long int l,r,num;
}q[maxn];

long long anss[maxn];

long long int block;
long long int ans=0;

bool cmp(node a,node b)
{
	return (a.r/block)==(b.r/block)?a.l<b.l:a.r<b.r;
 } 
/*
//根据奇偶性排序 
bool cmp(node a,node b)
{
	return pos[a.l]^pos[b.l]?pos[a.l]<pos[b.l]:pos[a.l]&1?a.r<b.r:a.r>b.r;
 } 
 */
 
inline void del(int x)
{
	sum[c[x]]--;
	ans-=2*sum[c[x]]+1;
}

inline void add(int x)
{
	sum[c[x]]++;
	ans+=2*sum[c[x]]-1;
}

int main()
{
	long long int n,m,k;
	cin>>n>>m>>k;
	block=sqrt(n);
	for(long long i=1;i<=n;i++)cin>>c[i];
	for(long long i=1;i<=m;i++)
	{
		cin>>q[i].l>>q[i].r;
		q[i].num=i;
	}
	sort(q+1,q+1+m,cmp);
	int l=1,r=0;
	for(long long i=1;i<=m;i++)
	{
		long long ql=q[i].l,qr=q[i].r;
		while(l<ql)
		{
			del(l++);
			//l++; 
		}
		while(l>ql)
		{
		//	l--;
			add(--l);
		}
		while(r<qr)
		{
		//	r++;
			add(++r);
		}
		while(r>qr)	
		{
			del(r--);
		//	r--;
		}
		anss[q[i].num]=ans;
	}
	for(long long i=1;i<=m;i++)
	printf("%lld\n",anss[i]);
	return 0;
}

扩展

莫队的优化(奇偶化排序)

我上面写的排序的函数,有一部分被注释掉,那是奇偶化排序,这是什么东西呢?
首先我们看组数据

//设块的大小为2
1 1
2 100
3 1
4 100

我们在处理完第一个块后,此时 l =2,r =100,我们要从当前状态移动到的第四个询问(l = 4, r = 100)需要移动两下,但是如果我们先移动到第三个询问再到第四个询问,就会多就是多下,这里该怎么优化?就要用到奇偶化排序
什么是奇偶化排序?
奇偶化排序即
对于属于奇数块的询问,r 按从小到大排序,
对于属于偶数块的排序,r 从大到小排序,
这样我们的 r 指针在处理完这个奇数块的问题后,将在返回的途中处理偶数块的问题,再向 n 移动处理下一个奇数块的问题,优化了 r 指针的移动次数,一般情况下,这种优化能让程序快 30% 左右。

struct node {
  int l, r, id;
  bool operator<(const node &x) const {
    if (l / unit != x.l / unit) return l < x.l;
    if ((l / unit) & 1)
      return r <
             x.r;  // 注意这里和下面一行不能写小于(大于)等于,否则会出错(详见下面的小细节)
    return r > x.r;
  }
};


bool cmp(node a,node b)
{
	return pos[a.l]^pos[b.l]?pos[a.l]<pos[b.l]:pos[a.l]&1?a.r<b.r:a.r>b.r;
 } 

带修改莫队

参考博文
加入单点修改操作
将时间也加入询问,这样询问就变成了一个三元组(li,ri,ti)
从询问i移动到询问j的时间复杂度变成O(|li-ri|+|lj-rj|+|ti-tj|)
设m==n(即询问次数等于数的数量)
将序列分块,块的大小是n的三分之二次方,一共有n的三分之一次方个块
。将询问按左端点所在块的编号为第一关键字,右端点所在块的编号为第二关键字,时间为第三关键字排序。
(和之前比就是多个时间排序)
排序条件:

int cmp(const node1 &a,const node1 &b)
{
    if (a.l/unit!=b.l/unit) return a.l/unit<b.l/unit;
    else if (a.r/unit!=b.r/unit) return a.r/unit<b.r/unit;
    else return a.x<b.x;
}

主函数维护答案:

void solve()
{
    int l=1,r=0,now=0;
    for (int i=1;i<=m;i++)
    {
        while (r<q[i].r) update(r+1,1),r++;
        while (r>q[i].r) update(r,-1),r--;
        while (l>q[i].l) update(l-1,1),l--;
        while (l<q[i].l) update(l,-1),l++;
        while (now<q[i].x) change(now+1,1,l,r),now++;
        while (now>q[i].x) change(now,-1,l,r),now--;
        ans[q[i].id]=tot;
    }
}

修改通过change实现:

void change(int bh,int z,int l,int r)
{
    if (ch[bh].x>=l&&ch[bh].x<=r) update(ch[bh].x,-1);   //删除从前的影响 
    swap(c[ch[bh].x],ch[bh].y);    
    if (ch[bh].x>=l&&ch[bh].x<=r) update(ch[bh].x,1);   //添加影响 
}

树上莫队

参考
我们要将树转化为线性结构,需要用到欧拉序,我们从根对这棵树进行dfs序,点进栈时记一个时间戳st,出栈时再记一个时间戳ed,如图:
在这里插入图片描述

这棵树的欧拉序就是(1,2,4,5,5,6,6,7,7,4,2,3,3)
在这里插入图片描述

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值