题解 P2709 【小B的询问】
upd on 2023.4.19:修缮
多次区间查询、不强制在线、
很显然:这就是莫队。以下视
一.什么是莫队
莫队其实就是一种优雅的暴力,它十分玄学巧妙地将分块和暴力结合在了一起,主要用来处理离线区间查询等问题。
由于莫队算法是由莫涛队长提出的,因此我们称这种算法为莫队。
二.莫队的思想
其实很简单:通过挪动区间的方式按某种顺序离线处理区间查询操作。
有三个关键词:挪动区间、按某种顺序、离线处理。
1.挪动区间
比如说第一次查询的区间是
那我们直接把
写成代码就是这样:
//ans1是上一次查询的左端点,ans2是上一次查询的右端点
//lef是当前查询的左端点,rig是当前查询的右端点
while(ans1>lef) ans1--,add(a[ans1]);//add是扩展函数
while(ans2<rig) ans2++,add(a[ans2]);
while(ans1<lef) del(a[ans1]),ans1++;//del是删除函数
while(ans2>rig) del(a[ans2]),ans2--;
add 函数和 del 函数:
//b是桶,统计每个数出现次数
inline void add(int x){
c+=2*b[x]+1;
//b[x]+1对于平方和c的影响(小学数学)
b[x]++;
}
inline void del(int x){
c-=2*b[x]-1;//同上
b[x]--;
}
2.按某种顺序
最严格的顺序应该是把每次区间求一个曼哈顿距离最小生成树。
但完全没必要(毕竟暴力)。
这时候分块就派上了用场~
把
我们先把这些区间按照左端点
再把
可以证明用这种方法最终挪动次数是
当然还有其它方式,但这样简单啊(逃)。
于是就有了这样的代码:
int block=sqrt(n);
inline bool cmp(node x,node y){
if((x.l-1)/block==(y.l-1)/block) return x.r<y.r;
return x.l/block<y.l/block;
}
……
sort(modui+1,modui+1+m,cmp);//modui就是莫队
3.离线处理
由于莫队是先把询问的区间存储起来再统一处理,当然是离线算法 qwq。
最后把每次询问对应的答案用数组存储起来就好啦。
三.莫队的实现
用一个结构体数组存储查询的区间以及下标。
再开一个数组记录答案,一个桶,当然还有原数组(废话)。
在转移时还要维护答案(不然你怎么记录)。
long long ans[maxn];//ans记录答案
int a[maxn],b[maxn];//a是原数组,b是桶
struct node{
int l,r,id;//l是左端点,r是右端点,id是下标
}modui[maxn];
OK,这就是普通莫队。
话不多说,直接放代码:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#define maxn 50001
#define ll long long
#define fo(x,y) for(register int i=x;i<=y;++i)
using namespace std;
ll ans[maxn],c; //注意longlong哦
int a[maxn],b[maxn],n,m,k,block;
struct node{
int l,r,id;
}modui[maxn];
inline bool cmp(node x,node y){//上文有解释
if((x.l-1)/block==(y.l-1)/block) return x.r<y.r;
return x.l/block<y.l/block;
}
inline int read(){//快读,不多说
int x=0,fh=1;
char ch=getchar();
while(!isdigit(ch)){
if(ch=='-') fh=-1;
ch=getchar();
}
while(isdigit(ch)){
x=(x<<1)+(x<<3)+ch-'0';
ch=getchar();
}
return x*fh;
}
inline void add(int x){//上文都解释过了
c+=2*b[x]+1;//维护答案
b[x]++;
}
inline void del(int x){
c-=2*b[x]-1;//维护答案
b[x]--;
}
int main(){
int lef,rig,ans1=1,ans2=0;
//ans1:上次的左端点 ans2:上次的右端点
n=read(),m=read(),k=read();
block=sqrt(n);
fo(1,n){
a[i]=read();
}
fo(1,m){
modui[i].l=read();
modui[i].r=read();
modui[i].id=i;
}
sort(modui+1,modui+1+m,cmp);
fo(1,m){
lef=modui[i].l,rig=modui[i].r;
//挪动操作:
while(ans1>lef) ans1--,add(a[ans1]);
while(ans2<rig) ans2++,add(a[ans2]);
while(ans1<lef) del(a[ans1]),ans1++;
while(ans2>rig) del(a[ans2]),ans2--;
ans[modui[i].id]=c;//存储答案
}
fo(1,m) printf("%lld\n",ans[i]);
return 0;
}
点个赞再走吧 qwq。