数据结构——莫队学习笔记

 莫队是种将问题存下来进行离线排序操作的暴力算法,将分块和暴力结合在了一起,主要用来处理离线区间查询等问题;时间复杂度O(n^2)

主要流程(重点是:分块排序)

1.将问题存下来,按照左边界分块进行存储(普通莫队的块大小为sqrt(n),带修莫队的块大小为n^(2/3)),块内按照右端点升序排序(优先级:块升序->右端点升序->(时间戳))

2.然后遍历排序后的问题,维护一个区间,不断向问题的区间靠拢,直到重合输出答案。

模板题

普通查询莫队

#include <iostream>
#include <cstring>
#include <queue>
#include <map>
#include <set>
#include <algorithm>
#include <cmath>
#include <vector>
#include <stack>
using namespace std;
typedef long long LL;
typedef pair<LL,LL> PII;//开long long,求求你了,记得开long long 
const LL N=5e4+10;
const LL INF=1e18;
const double small=1e-16;
//千万不要用puts()和gets(),求求你了 
LL a[N];

/*普通莫队
优雅的暴力:离线排序暴力(离线:把问题存下来处理完成后一起输出答案)
把问题分块(单纯查找通常为m^(1/2)的大小,然后按照右端点从小到大排序)
然后维护两个指针,不断向问题的左右下标移动,更新答案 

*/

struct Quetion
{
	LL l,r;
	LL id;
}que[N];
LL res[N];
LL T;
LL cnt[N]={0};
LL ans=0;
bool cmd(Quetion a,Quetion b)
{
	if(a.l/T!=b.l/T)return a.l/T<b.l/T;
	return a.r<b.r;
}

void add(LL a)//在维护区域内加上一个数 
{
	ans-=cnt[a]*cnt[a];
	cnt[a]++;
	ans+=cnt[a]*cnt[a];
}

void del(LL a)//在维护区域内减去一个数 
{
	ans-=cnt[a]*cnt[a];
	cnt[a]--;
	ans+=cnt[a]*cnt[a];
}

void solve()
{
	LL n,m,k;
	cin>>n>>m>>k;
	T=sqrt(m);
	for(int i=1;i<=n;i++)cin>>a[i];
	for(int i=1;i<=m;i++)
	{
		cin>>que[i].l>>que[i].r;
		que[i].id=i;
	}
	sort(que+1,que+1+m,cmd);//对问题进行排序,关键步骤 
	LL l=1,r=0;
	for(int i=1;i<=n;i++)
	{
		while(que[i].l>l)del(a[l++]);
		while(que[i].l<l)add(a[--l]);//维护一个区域 
		while(que[i].r>r)add(a[++r]);
		while(que[i].r<r)del(a[r--]);
		res[que[i].id]=ans;
	}
	for(int i=1;i<=m;i++)cout<<res[i]<<endl;
}

int main()
{
    
	int t=1;
	//cin>>t;
	while(t--)
	{
		solve();
	}
	return 0;
}

带修莫队

#include <iostream>
#include <cstring>
#include <queue>
#include <map>
#include <set>
#include <algorithm>
#include <cmath>
#include <vector>
#include <stack>
using namespace std;
typedef long long LL;
typedef pair<LL,LL> PII;//开long long,求求你了,记得开long long 
const LL N=1e6+10;
const LL INF=1e18;
const double small=1e-16;
LL T;
//千万不要用puts()和gets(),求求你了 
LL a[N];

/*
带修莫队
在原来的基础上加上了一个时间维 
*/
struct Que
{
	LL l,r;
	LL id;
	LL tim;
}que[N];
LL idx=0,idy=0;
struct change
{
	LL p,v;
}chan[N];

bool cmd(Que a,Que b)
{
	if(a.l/T==b.l/T)//按照块->右端点->时间优先级排序 
	{
		if(a.r==b.r)
		{
			return a.tim<b.tim;
		}
		else return a.r<b.r;
	}
	else return a.l/T<b.l/T;
}
LL res[N];
LL ans=0;
LL cnt[N];

void add(LL a)
{
	if(!cnt[a])ans++;
	cnt[a]++;
}

void del(LL a)
{
	if(cnt[a]==1)ans--;
	cnt[a]--;
}

void solve()
{
	LL n,m;
	cin>>n>>m;
	for(int i=1;i<=n;i++)cin>>a[i];
	for(int i=1;i<=m;i++)
	{
		char op[2];
		LL l,r;
		scanf("%s%lld%lld",op,&l,&r);
		if(op[0]=='Q')que[++idx]={l,r,idx,idy};
		else chan[++idy]={l,r};
	}
	T=pow(m,0.666);//带修莫队不能用开方,要用(2/3)次方 
	sort(que+1,que+1+idx,cmd);
	LL l=1,r=0;
	LL now=0;
	for(int i=1;i<=idx;i++)
	{
		while(l>que[i].l)add(a[--l]);
		while(l<que[i].l)del(a[l++]);
		while(r>que[i].r)del(a[r--]);
		while(r<que[i].r)add(a[++r]);
		while(now<que[i].tim)//时间戳改变 
		{
			++now;//先改 
			int p=chan[now].p,v=chan[now].v;
			if(p>=l&&p<=r)del(a[p]),add(v);
			swap(a[p],chan[now].v);//存储恢复改变时需要改成的值 
		}
		while(now>que[i].tim)
		{
			int p=chan[now].p,v=chan[now].v;
			if(p>=l&&p<=r)del(a[p]),add(v);
			swap(a[p],chan[now].v);//存储恢复改变时需要改成的值 
			--now;//后改 
		}
	res[que[i].id]=ans;
	}
	for(int i=1;i<=idx;i++)cout<<res[i]<<endl;
}

int main()
{
    
	int t=1;
    //cin>>t;
	while(t--)
	{
		solve();
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值