[数据结构] 莫队
[数据结构] 莫队
莫队是一种基于分块思想的离线算法。
本文章介绍初等莫队。
今天才学会带修
普通莫队
一个板子题,要求维护平方和。
直接给出公式吧:
\(ans=\frac{\sum_{i=1}^c x_i^2-(R-L+1)}{(R-L+1)(R-L)}\)
莫队的基本思想
对左右端点进行分块,左端点所在块为第一关键字,右端点所在块为第二关键字,这样就保证了普通莫队的复杂度是 \(\text O(n\sqrt{n})\) .
初始时 \(l=1,r=0\),意义是 \([l,r]\) 这段区间已经处理过了,然后根据询问的左右端点不断扩展或者撤销。(具体用四个循环实现)
细节部分
-
特判 \(l=r\) 的情况。
-
防止死循环。
-
一定注意左右端点 \(l,r\) 的把控,细节想到位。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
template <typename T>
inline T read(){
T x=0;char ch=getchar();bool fl=false;
while(!isdigit(ch)){if(ch=='-')fl=true;ch=getchar();}
while(isdigit(ch)){
x=(x<<3)+(x<<1)+(ch^48);ch=getchar();
}
return fl?-x:x;
}
const int maxn = 5e4 + 10;
#define LL long long
int c[maxn],t[maxn];
struct node{
int l,r,id;
LL a,b;
}a[maxn];
LL gcd(LL a,LL b){
if(!b)return a;
return gcd(b,a%b);
}
int block[maxn],n1,n,m;
#define read() read<int>()
#include <cmath>
inline bool cmp(node a,node b){
if(block[a.l]!=block[b.l])return a.l<b.l;
return a.r<b.r;
}
inline bool cmp_id(node a,node b){
return a.id<b.id;
}
LL ans;
inline void update(int p,int val){
ans-=t[c[p]]*t[c[p]];
t[c[p]]+=val;
ans+=(LL)t[c[p]]*t[c[p]];
}
void solve(){
for(int i=1,l=1,r=0;i<=m;i++){
while(r<a[i].r)update(r+1,1),r++;
while(l>a[i].l)update(l-1,1),l--;
while(r>a[i].r)update(r,-1),r--;
while(l<a[i].l)update(l,-1),l++;
if(a[i].l==a[i].r){
a[i].a=0;a[i].b=1;continue;
}
a[i].a=ans-(a[i].r-a[i].l+1);
a[i].b=1LL*(a[i].r-a[i].l+1)*(a[i].r-a[i].l);
LL g=gcd(a[i].a,a[i].b);
a[i].a/=g;a[i].b/=g;
}
}
int main(){
n=read();m=read();n1=(int)sqrt(n+0.5);
for(int i=1;i<=n;i++)c[i]=read();
for(int i=1;i<=n;i++)block[i]=(i-1)/n1+1;
for(int i=1;i<=m;i++){
a[i].l=read();a[i].r=read();a[i].id=i;
}
sort(a+1,a+1+m,cmp);
solve();
sort(a+1,a+1+m,cmp_id);
for(int i=1;i<=m;i++)printf("%lld/%lld\n",a[i].a,a[i].b);
return 0;
}
带修改莫队
与普通莫队不同的是,带修改莫队会 在一个特定的时间点进行对信息的修改 ,这就需要我们把二元组改为三元组 \((l,r,time)\) 。
其中时间按照第三关键字排序,块长改为 \(n^{\frac{2}{3}}\)。
复杂度为 \(\text O(n^{\frac{5}{3}})\) 。
做法和普通莫队的区别:
每次扩展区间的时候(不要以为时间是顺序的),看当前需要修改的操作,如果这个操作在时间轴中包含于扩展的区间 \([l,r]\) ,看它和当前查询操作 \(time\) 的关系,进行合理的 撤销或修改。
需要自己慢慢体会。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
template <typename T>
inline T read(){
T x=0;char ch=getchar();bool fl=false;
while(!isdigit(ch)){if(ch=='-')fl=true;ch=getchar();}
while(isdigit(ch)){
x=(x<<3)+(x<<1)+(ch^48);ch=getchar();
}
return fl?-x:x;
}
const int maxn = 1333333 + 10;
#include <cmath>
int n,m,c[maxn],ct[maxn];
int mem[maxn][3],c1,c2,tot[maxn];
int Ans[maxn],n1;
struct node{
int l,r,t,id;
bool operator <(const node &x)const{
if(l/n1 == x.l/n1){
if(r/n1 == x.r/n1)
return t<x.t;
return r<x.r;
}
return l<x.l;
}
}a[maxn];
int ans;
#define read() read<int>()
inline void del(int x){
if(--tot[x]==0)ans--;
}
inline void add(int x){
if(++tot[x]==1)ans++;
}
int main(){
n=read();m=read();n1=pow(n,0.666666);
for(int i=1;i<=n;i++){
c[i]=read();ct[i]=c[i];
}
for(int i=1;i<=m;i++){
char op;cin>>op;int x=read(),b=read();
if(op=='Q'){
c1++;a[c1]=(node){x,b,c2,c1};
}
else {
c2++;mem[c2][0]=x;mem[c2][1]=ct[x];mem[c2][2]=ct[x]=b;//鏇存敼浣嶇疆锛屽師鏉ワ紝鐜板湪棰滆壊
}
}
sort(a+1,a+1+c1);
int l=1,r=0,last=1;
for(int i=1;i<=c1;i++){
while(last<=a[i].t){
if(l<=mem[last][0] && mem[last][0]<=r)
del(mem[last][1]),add(mem[last][2]);
c[mem[last][0]]=mem[last][2];
last++;
}
while(last-1>a[i].t){
if(l<=mem[last-1][0] && mem[last-1][0]<=r)
del(mem[last-1][2]),add(mem[last-1][1]);
c[mem[last-1][0]]=mem[last-1][1];
last--;
}
while(r<a[i].r)add(c[r+1]),r++;
while(l>a[i].l)add(c[l-1]),l--;
while(r>a[i].r)del(c[r]),r--;
while(l<a[i].l)del(c[l]),l++;
Ans[a[i].id]=ans;
}
for(int i=1;i<=c1;i++)printf("%d\n",Ans[i]);
return 0;
}