莫队是种将问题存下来进行离线排序操作的暴力算法,将分块和暴力结合在了一起,主要用来处理离线区间查询等问题;时间复杂度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;
}