莫队 学习笔记
一.莫队
1.普通莫队
一般使用条件:
-
操作:都是区间查询(不资瓷修改)
-
能 \(O(1)\) 从 \([l,r]\) 推出 \([l,r+1]\quad [l-1,r]\) 的答案
-
复杂度:\(O(n\sqrt n)\) 要能跑过
板子题
开桶记录记录当前区间内每个值出现的个数 转移时若 \(cnt[x]=0\quad ans\)++ 即可
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define inl inline
#define int ll
#define gc getchar
#define pc putchar
const int N=2e6+5;
const int M=2e6+5;
const int inf=0x7fffffff;
const int mod=10086;
inl int read(){
int x=0,f=1;char c=gc();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=gc();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=gc();}
return x*f;
}
inl void write(int x){
if(x<0){pc('-');x=-x;}
if(x>9)write(x/10);
pc(x%10+'0');
}
inl void writel(int x){write(x);pc('\n');}
inl void writei(int x){write(x);pc(' ');}
inl void debug1(int x){pc('?');write(x);pc('\n');}
inl void debug2(int x){pc('#');write(x);pc('\n');}
int n,m,len,a[N],cnt[M],bl[N],ans,l=1,r=0,res[N];
struct node{
int l,r,id;
friend bool operator<(node a,node b){
return (bl[a.l]^bl[b.l])?bl[a.l]<bl[b.l]:(bl[a.l]&1)?a.r<b.r:a.r>b.r;
}
}q[N];
inl void add(int x){
ans+=(!cnt[x]);
cnt[x]++;
}
inl void del(int x){
cnt[x]--;
ans-=(!cnt[x]);
}
inl void getans(node q){
while(r<q.r)add(a[++r]);
while(l>q.l)add(a[--l]);
while(l<q.l)del(a[l++]);
while(r>q.r)del(a[r--]);
res[q.id]=ans;
}
signed main(){
n=read();len=sqrt(n);
for(int i=1;i<=n;i++){
a[i]=read();
bl[i]=(i-1)/len+1;
}
m=read();
for(int i=1;i<=m;i++)
q[i].l=read(),q[i].r=read(),q[i].id=i;
sort(q+1,q+m+1);
for(int i=1;i<=m;i++)getans(q[i]);
for(int i=1;i<=m;i++)writel(res[i]);
return 0;
}
CF617E-XOR and Favorite Number
考虑异或的性质:一个数被异或两次答案是 \(0\)
我们维护一个异或和数组 则 \(x_l\oplus x_{l+1}\oplus...\oplus x_r=sum_r\oplus sum_{l-1}\)
然后就是莫队板子了 在桶里查询 \(k\oplus x\)
\([l,r]\) 的答案是 \(sum_r\oplus sum_{l-1}\) 所以输入要把 \(l\ -1\)
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define inl inline
#define int ll
#define gc getchar
#define pc putchar
const int N=1e5+5;
const int M=2e6+5;
const int inf=0x7fffffff;
const int mod=10086;
inl int read(){
int x=0,f=1;char c=gc();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=gc();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=gc();}
return x*f;
}
inl void write(int x){
if(x<0){pc('-');x=-x;}
if(x>9)write(x/10);
pc(x%10+'0');
}
inl void writel(int x){write(x);pc('\n');}
inl void writei(int x){write(x);pc(' ');}
inl void debug1(int x){pc('?');write(x);pc('\n');}
inl void debug2(int x){pc('#');write(x);pc('\n');}
int n,m,k,len,a[N],cnt[M],bl[N],ans,l=1,r=0,res[N];
struct node{
int l,r,id;
friend bool operator<(node a,node b){
return (bl[a.l]^bl[b.l])?bl[a.l]<bl[b.l]:(bl[a.l]&1)?a.r<b.r:a.r>b.r;
}
}q[N];
inl void add(int x){
ans+=cnt[k^x];
cnt[x]++;
}
inl void del(int x){
cnt[x]--;
ans-=cnt[k^x];
}
inl void getans(node q){
while(r<q.r)add(a[++r]);
while(l>q.l)add(a[--l]);
while(l<q.l)del(a[l++]);
while(r>q.r)del(a[r--]);
res[q.id]=ans;
}
signed main(){
n=read();m=read();k=read();len=sqrt(n);
for(int i=1;i<=n;i++){
a[i]=read()^a[i-1];
bl[i]=(i-1)/len+1;
}
for(int i=1;i<=m;i++)
q[i].l=read()-1,q[i].r=read(),q[i].id=i;
sort(q+1,q+m+1);
for(int i=1;i<=m;i++)getans(q[i]);
for(int i=1;i<=m;i++)writel(res[i]);
return 0;
}
2.带修莫队
带修莫队,顾名思义,就是带修改的莫队(废话)
只需比普通莫队多维护第三维时间即可
分块大小为 \(n^{\tfrac{2}{3}}\) 有最优复杂度 \(O(n^{\tfrac{5}{3}})\)
板子题 直接放代码
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define inl inline
#define gc getchar
#define pc putchar
const int N=1e6+5;
const int M=1e6+5;
const int inf=0x7fffffff;
const int mod=10086;
inl int read(){
int x=0,f=1;char c=gc();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=gc();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=gc();}
return x*f;
}
inl void write(int x){
if(x<0){pc('-');x=-x;}
if(x>9)write(x/10);
pc(x%10+'0');
}
inl void writel(int x){write(x);pc('\n');}
inl void writei(int x){write(x);pc(' ');}
inl void debug1(int x){pc('?');write(x);pc('\n');}
inl void debug2(int x){pc('#');write(x);pc('\n');}
int n,m,len,a[N],cnt[N],tot[N],bl[N],ans,l=1,r=0,t=0,res[N],cntp,cntq;
char op;
struct node{
int l,r,t,id;
friend bool operator<(node a,node b){
return (bl[a.l]^bl[b.l])?bl[a.l]<bl[b.l]:(bl[a.r]^bl[b.r])?bl[a.r]<bl[b.r]:a.t<b.t;
}
}q[N];
struct Node{
int pos,col;
}p[N];
inl void add(int x){
ans+=(!cnt[x]);
cnt[x]++;
}
inl void del(int x){
cnt[x]--;
ans-=(!cnt[x]);
}
inl void upd(node q,Node &p){
if(q.l<=p.pos&&p.pos<=q.r){
del(a[p.pos]);
add(p.col);
}
swap(a[p.pos],p.col);
}
inl void getans(node q){
while(r<q.r)add(a[++r]);
while(l>q.l)add(a[--l]);
while(l<q.l)del(a[l++]);
while(r>q.r)del(a[r--]);
while(t<q.t)upd(q,p[++t]);
while(t>q.t)upd(q,p[t--]);
res[q.id]=ans;
}
signed main(){
n=read();m=read();len=pow(n,0.666);
for(int i=1;i<=n;i++){
a[i]=read();
bl[i]=(i-1)/len+1;
}
for(int i=1;i<=m;i++){
scanf("%s",&op);
if(op=='Q')
q[++cntq]={read(),read(),cntp,cntq};
else
p[++cntp]={read(),read()};
}
sort(q+1,q+cntq+1);
for(int i=1;i<=cntq;i++)getans(q[i]);
for(int i=1;i<=cntq;i++)writel(res[i]);
return 0;
}
统计每个数出现个数很简单 统计每种个数是否出现也很简单
那么考虑如何求出没出现过的个数的 \(mex\)
其实暴力求就可以了()
复杂度证明:设没出现过的个数的 \(mex=x\) 则一定会有出现 \(1\) 次、出现 \(2\) 次 \(...\) 出现 \(x-1\) 次的数
则区间内数的数量 \(=\ \sum\limits_{i=1}^{x-1}i\ =\ \dfrac{x(x-1)}{2}\le n\)
因此单次求 \(mex\) 复杂度 \(O(\sqrt n)\) 求 \(mex\) 总复杂度 \(O(n\sqrt n)\)
总复杂度还是 \(O(n^{\tfrac{5}{3}})\)
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define inl inline
#define gc getchar
#define pc putchar
const int N=1e6+5;
const int M=1e6+5;
const int inf=0x7fffffff;
const int mod=10086;
inl int read(){
int x=0,f=1;char c=gc();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=gc();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=gc();}
return x*f;
}
inl void write(int x){
if(x<0){pc('-');x=-x;}
if(x>9)write(x/10);
pc(x%10+'0');
}
inl void writel(int x){write(x);pc('\n');}
inl void writei(int x){write(x);pc(' ');}
inl void debug1(int x){pc('?');write(x);pc('\n');}
inl void debug2(int x){pc('#');write(x);pc('\n');}
int n,m,len,a[N],cnt[N],tot[N],bl[N],ans,l=1,r=0,t=0,res[N],cntp,cntq,op,lsh[N],num;
struct node{
int l,r,t,id;
friend bool operator<(node a,node b){
return (bl[a.l]^bl[b.l])?bl[a.l]<bl[b.l]:(bl[a.r]^bl[b.r])?bl[a.r]<bl[b.r]:a.t<b.t;
}
}q[N];
struct Node{
int pos,col;
}p[N];
inl void add(int x){
tot[cnt[x]]--;
tot[++cnt[x]]++;
}
inl void del(int x){
tot[cnt[x]--]--;
tot[cnt[x]]++;
}
inl void upd(node q,Node &p){
if(q.l<=p.pos&&p.pos<=q.r){
del(a[p.pos]);
add(p.col);
}
swap(a[p.pos],p.col);
}
inl void getans(node q){
while(r<q.r)add(a[++r]);
while(l>q.l)add(a[--l]);
while(l<q.l)del(a[l++]);
while(r>q.r)del(a[r--]);
while(t<q.t)upd(q,p[++t]);
while(t>q.t)upd(q,p[t--]);
for(ans=1;tot[ans];ans++);
res[q.id]=ans;
}
signed main(){
n=read();m=read();len=pow(n,0.666);
for(int i=1;i<=n;i++){
a[i]=lsh[++num]=read();
bl[i]=(i-1)/len+1;
}
for(int i=1;i<=m;i++){
op=read();
if(op==1)
q[++cntq]={read(),read(),cntp,cntq};
else{
p[++cntp]={read(),read()};lsh[++num]=p[cntp].col;
}
}
sort(q+1,q+cntq+1);sort(lsh+1,lsh+num+1);
int t=unique(lsh+1,lsh+num+1)-lsh-1;
for(int i=1;i<=n;i++)
a[i]=lower_bound(lsh+1,lsh+t+1,a[i])-lsh;
for(int i=1;i<=cntp;i++)
p[i].col=lower_bound(lsh+1,lsh+t+1,p[i].col)-lsh;
for(int i=1;i<=cntq;i++)getans(q[i]);
for(int i=1;i<=cntq;i++)writel(res[i]);
return 0;
}
3.回滚莫队
莫队滚起来了
求区间内 \(a[i]*cnt[a[i]]\) 最大值
显然 区间扩大时答案很好维护 缩小时则无法 \(O(1)\) 修改
于是我们让莫队滚起来 这样只有增加操作了
\(a[i]\le 1e9\) 记得开 \(long\ long\) 和离散化
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define inl inline
#define int ll
#define gc getchar
#define pc putchar
const int N=1e6+5;
const int M=1e6+5;
const int inf=0x7fffffff;
const int mod=10086;
inl int read(){
int x=0,f=1;char c=gc();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=gc();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=gc();}
return x*f;
}
inl void write(int x){
if(x<0){pc('-');x=-x;}
if(x>9)write(x/10);
pc(x%10+'0');
}
inl void writel(int x){write(x);pc('\n');}
inl void writei(int x){write(x);pc(' ');}
inl void debug1(int x){pc('?');write(x);pc('\n');}
inl void debug2(int x){pc('#');write(x);pc('\n');}
int n,m,len,a[N],cnt[N],tot[N],bl[N],ans,l=1,r=0,res[N],pos=1,lsh[N];
struct node{
int l,r,id;
friend bool operator<(node a,node b){
return (bl[a.l]^bl[b.l])?bl[a.l]<bl[b.l]:a.r<b.r;
}
}q[N];
inl void getans(int k){
int l=min(k*len,n)+1,r=min(k*len,n),ans=0,pst;
for(;pos<=m&&bl[q[pos].l]==k;pos++){
node qus=q[pos];
int lid=bl[qus.l],rid=bl[qus.r];
if(lid==rid){
for(int i=qus.l;i<=qus.r;i++){
tot[a[i]]++;
res[qus.id]=max(res[qus.id],tot[a[i]]*lsh[a[i]]);
}
for(int i=qus.l;i<=qus.r;i++)
tot[a[i]]--;
continue;
}
while(r<qus.r){
cnt[a[++r]]++;
ans=max(ans,cnt[a[r]]*lsh[a[r]]);
}
pst=ans;
while(l>qus.l){
cnt[a[--l]]++;
ans=max(ans,cnt[a[l]]*lsh[a[l]]);
}
res[qus.id]=ans;
ans=pst;
while(l<min(k*len,n)+1)
cnt[a[l++]]--;
}
while(r>min(k*len,n))
cnt[a[r--]]--;
}
signed main(){
n=read();m=read();len=sqrt(n);
for(int i=1;i<=n;i++){
a[i]=lsh[i]=read();
bl[i]=(i-1)/len+1;
}
for(int i=1;i<=m;i++)
q[i]={read(),read(),i};
sort(q+1,q+m+1);sort(lsh+1,lsh+n+1);
int t=unique(lsh+1,lsh+n+1)-lsh-1;
for(int i=1;i<=n;i++)a[i]=lower_bound(lsh+1,lsh+t+1,a[i])-lsh;
for(int i=1;i<=bl[n];i++)getans(i);
for(int i=1;i<=m;i++)writel(res[i]);
return 0;
}
首先这题有个 \(\log\) 的主席树做法:维护每个值最后一次出现的下标,每次再 \(r\) 中查找最小的、最后一次出现的下标小于 \(l\) 的值
但是不想写主席树怎么办呢
显然每次缩小区间如果 \(cnt=0\) 能做到 \(O(1)\) 转移
于是我们就可以用回滚莫队碾掉这道题了
不要随便memset 很容易使复杂度假掉
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define inl inline
#define int ll
#define gc getchar
#define pc putchar
const int N=1e6+5;
const int M=1e6+5;
const int inf=0x7fffffff;
const int mod=10086;
inl int read(){
int x=0,f=1;char c=gc();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=gc();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=gc();}
return x*f;
}
inl void write(int x){
if(x<0){pc('-');x=-x;}
if(x>9)write(x/10);
pc(x%10+'0');
}
inl void writel(int x){write(x);pc('\n');}
inl void writei(int x){write(x);pc(' ');}
inl void debug1(int x){pc('?');write(x);pc('\n');}
inl void debug2(int x){pc('#');write(x);pc('\n');}
int n,m,len,a[N],cnt[N],tot[N],bl[N],ans,l,r,res[N],pos=1,pans;
struct node{
int l,r,id;
friend bool operator<(node a,node b){
return (bl[a.l]^bl[b.l])?bl[a.l]<bl[b.l]:a.r>b.r;
}
}q[N];
inl void getans(int k){
int pst;ans=pans;
while(r<n)
cnt[a[++r]]++;
while(l<(k-1)*len+1){
cnt[a[l++]]--;
if(!cnt[a[l-1]])ans=min(ans,a[l-1]);
}
pans=ans;
for(;pos<=m&&bl[q[pos].l]==k;pos++){
node qus=q[pos];
int lid=bl[qus.l],rid=bl[qus.r];
if(lid==rid){
for(int i=qus.l;i<=qus.r;i++)
tot[a[i]]++;
while(tot[res[qus.id]])res[qus.id]++;
for(int i=qus.l;i<=qus.r;i++)
tot[a[i]]--;
continue;
}
while(r>qus.r){
cnt[a[r--]]--;
if(!cnt[a[r+1]])ans=min(ans,a[r+1]);
}
pst=ans;
while(l<qus.l){
cnt[a[l++]]--;
if(!cnt[a[l-1]])ans=min(ans,a[l-1]);
}
res[qus.id]=ans;
ans=pst;
while(l>(k-1)*len+1)
cnt[a[--l]]++;
}
}
signed main(){
n=read();m=read();len=sqrt(n);
for(int i=1;i<=n;i++){
a[i]=read();
bl[i]=(i-1)/len+1;
}
for(int i=1;i<=m;i++)
q[i]={read(),read(),i};
sort(q+1,q+m+1);
for(int i=1;i<=n;i++)cnt[a[i]]++;
while(cnt[pans])pans++;
l=1,r=n;
for(int i=1;i<=bl[n];i++)getans(i);
for(int i=1;i<=m;i++)writel(res[i]);
return 0;
}