昨天重温了一下CaptainMo的职业生涯 莫队的模板,看了下别人的博客,把三个板子打了,做练习前先小小总结了一下吧。
一. 基础莫队算法
莫队算法 = 离线 + 暴力 + 分块,它通常用于不修改只查询的一类区间问题,复杂度为
主要就是通过排序过后再处理询问能优化暴力,排序则是利用分块,至于为什么更优,附张别人博客看到的图。
这就很显然图二的走法更短,然后 编码时,还可以对排序做一个小优化:奇偶性排序,让奇数块和偶数块的排序相反。例如左端点L都在奇数块,则对R从大到小排序;若L在偶数块,则对R从小到大排序(反过来也可以:奇数块从小到大,偶数块从大到小)。
luogu P1972 HH的项链代码,这代码不能拿全分(不会卡常的屑 ),但正确性无误
#include<bits/stdc++.h>
const int N=1e6+10;
using namespace std;
int n,m,a[N],cnt[N],ans[N],block,id[N],t,L,R,sum;
struct ask
{
int l;
int r;
int Id;
}q[N];
inline int Read()
{
int x=0,f=1;
char ch;
while(ch>'9'||ch<'0')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=x*10+ch-'0';
ch=getchar();
}
return f*x;
}
bool comp(ask a,ask b)
{
if(id[a.l]!=id[b.l]) return id[a.l]<id[b.l];
if((id[a.l]%2)==1) return a.r>b.r;
else return a.r<b.r;
}
void add(int x)
{
x=a[x];
if(!cnt[x]) sum++;
cnt[x]++;
}
void del(int x)
{
x=a[x];
cnt[x]--;
if(!cnt[x]) sum--;
}
int main()
{
n=Read();
block=sqrt(n)+1;
t=n/block;
if(n%block) t++;
for(int i=1;i<=n;i++) a[i]=Read(),id[i]=(i-1)/block+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+1+m,comp);
L=1,R=0;
for(int i=1;i<=m;i++)
{
while(L<q[i].l) del(L),L++;
while(L>q[i].l) L--,add(L);
while(R<q[i].r) R++,add(R);
while(R>q[i].r) del(R),R--;
ans[q[i].Id]=sum;
}
for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
return 0;
}
二. 带修改的莫队
如果是比较简单的“单点修改”,也能应用莫队算法,复杂度
还是有道例题luogu P1903数颜色
如果用莫队算法求解,必须离线,先把查询操作和修改操作分别记录下来。记录查询操作的时候,增加一个变量,记录本次查询前做少次修改。
.
如果没有修改,就是基础莫队,一个查询的左右端点是[L, R]。加上修改之后,一个查询表示为(L, R, t),t表示在查询[L, R]前进行次修改操作。可以把t理解为“时间”,t的范围是1 ≤ t ≤ m,m是操作次数。
.
反正就是要多考虑修改操作对查询的影响,又因为是单点修改,可以直接暴力,查询中就多了这么一截
void CaptainMo()
{
L=1,R=now=0;
for(int i=1;i<=qnum;i++)
{
while(L<q[i].l) del(a[L]),L++;
while(L>q[i].l) L--,add(a[L]);
while(R<q[i].r) R++,add(a[R]);
while(R>q[i].r) del(a[R]),R--;
while(now>q[i].pre) update(now,i),now--;//需要还原修改
while(now<q[i].pre) now++,update(now,i);//需要继续修改
ans[q[i].Id]=sum;
}
}
然后就是一些小细节的不同,比如分块大小应该为
因为这样更快。排序时右端点r也按所在块的序号大小排,不是r大小。还有就是在修改时要注意写这句swap(a[x],p[pos].val);
为什么的话模拟一下就知道了,当时理解了好久才看懂。
代码
#include<bits/stdc++.h>
using namespace std;
const int N=1400000;
int n,m,L,R,x,y,now,id[N],block,a[N],ans[N],cnt[N],qnum,pnum,sum;
struct ask
{
int l;
int r;
int Id;
int pre;
}q[N];
struct upd
{
int st;
int val;
}p[N];
inline int Read()
{
int x=0,f=1;
char ch;
while(ch>'9'||ch<'0')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=x*10+ch-'0';
ch=getchar();
}
return f*x;
}
bool comp(ask A,ask b)
{
if(id[A.l]!=id[b.l]) return id[A.l]<id[b.l];
if(id[A.r]!=id[b.r]) return id[A.r]<id[b.r];
return A.pre<b.pre;
}
void add(int x)
{
if(!cnt[x]) sum++;
cnt[x]++;
}
void del(int x)
{
cnt[x]--;
if(!cnt[x]) sum--;
}
void update(int pos,int i)
{
int x=p[pos].st;
if(x>=q[i].l&&x<=q[i].r)
{
cnt[a[x]]--;
if(!cnt[a[x]]) sum--;
if(!cnt[p[pos].val]) sum++;
cnt[p[pos].val]++;
}
swap(a[x],p[pos].val);
}
void CaptainMo()
{
L=1,R=now=0;
for(int i=1;i<=qnum;i++)
{
while(L<q[i].l) del(a[L]),L++;
while(L>q[i].l) L--,add(a[L]);
while(R<q[i].r) R++,add(a[R]);
while(R>q[i].r) del(a[R]),R--;
while(now>q[i].pre) update(now,i),now--;
while(now<q[i].pre) now++,update(now,i);
ans[q[i].Id]=sum;
}
}
int main()
{
n=Read(),m=Read();
double X=pow(n,2.0/3.0);
block=(int)X;
for(int i=1;i<=n;i++) a[i]=Read(),id[i]=(i-1)/block+1;
for(int i=1;i<=m;i++)
{
char s;
cin>>s,x=Read(),y=Read();
if(s=='Q')
q[++qnum].l=x,q[qnum].r=y,q[qnum].Id=qnum,q[qnum].pre=pnum;
else
p[++pnum].st=x,p[pnum].val=y;
}
sort(q+1,q+1+qnum,comp);
CaptainMo();
for(int i=1;i<=qnum;i++) printf("%d\n",ans[i]);
return 0;
}
三. 树上莫队
基础莫队和带修改的莫队操作的都是一维数组。基于其他的数据结构的问题,如果能转换成一维数组而且是区间问题,那么也能应用莫队算法。
典型的例子是树形结构上的路径问题,可以利用“欧拉序”把整棵树的结点顺序转化为一个一维数组,路径问题也变成了区间问题,就能利用莫队算法求解。下面还是有道例题。
SP10707 COT2 - Count on a tree II
相信学过树剖后对树上的节点存到队列已经熟悉,然后就考虑(u, v)上的路径有哪些结点?首先计算出u、v的lca(u, v)(最近公共祖先),然后讨论两种情况:
(1)lca(u, v) = u或lca(u, v) = v,这种情况最简单,可以直接做,不多说了。
(2)lca(u, v) ≠ u且lca(u, v) ≠ v,这种情况其实也不复杂,就在查询时多查询一个lca再计算答案,然后再查询一次消除lca对目前的sum值影响。
至于如何忽略掉区间内出现了两次的点,这个很简单,我们多记录一个vis[x],表示x这个点有没有被加入,每次处理的时候如果vis[x]=0则需要添加节点;如果vis[x]=1则需要删除节点。
代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+100;
int vis[N],L,R,cnt[N],f[40010][20],n,m,sum,b[N],a[N],len,ans[N],idx[N],Idx[N],ref[N],index_,id[N],block,dep[N],first[N],Next[N],to[N],tot;
struct ask
{
int l;
int r;
int id;
int zx;
}q[N];
int Read()
{
int x=0,f=1;
char ch=getchar();
while(!isdigit(ch))
{
if(ch=='-') f=-1;
ch=getchar();
}
while(isdigit(ch))
{
x=(x<<1)+(x<<3)+ch-'0';
ch=getchar();
}
return f*x;
}
void add(int a,int b)
{
tot++;
Next[tot]=first[a];
to[tot]=b;
first[a]=tot;
}
void dfs(int u,int fa)
{
index_++;
idx[u]=index_;
ref[index_]=u;
dep[u]=dep[fa]+1;
for(int i=1;i<20;i++) f[u][i]=f[f[u][i-1]][i-1];
for(int i=first[u];i;i=Next[i])
{
int v=to[i];
if(v==fa) continue;
f[v][0]=u;
dfs(v,u);
}
Idx[u]=++index_,ref[index_]=u;
}
int lca(int u,int v)
{
if(dep[u]<dep[v]) swap(u,v);
for(int i=19;i>=0;i--) if(dep[f[u][i]]>=dep[v]) u=f[u][i];
if(u==v) return u;
for(int i=19;i>=0;i--) if(f[u][i]!=f[v][i]) u=f[u][i],v=f[v][i];
return f[u][0];
}
bool comp(ask a,ask b)
{
if(id[a.l]!=id[b.l]) return id[a.l]<id[b.l];
if(id[a.l]%2==1) return a.r>b.r;
else return a.r<b.r;
}
void update(int x)
{
vis[x]^=1;
if(vis[x])
{
cnt[a[x]]++;
if(cnt[a[x]]==1) sum++;
}
else
{
cnt[a[x]]--;
if(!cnt[a[x]]) sum--;
}
}
void CaptainMo()
{
L=1,R=0;
for(int i=1;i<=m;i++)
{
while(L>q[i].l) L--,update(ref[L]);
while(L<q[i].l) update(ref[L]),L++;
while(R>q[i].r) update(ref[R]),R--;
while(R<q[i].r) R++,update(ref[R]);
if(q[i].zx) update(q[i].zx);
ans[q[i].id]=sum;
if(q[i].zx) update(q[i].zx);
}
}
int main()
{
n=Read(),m=Read();
block=(int)pow(n,2.0/3.0);
for(int i=1;i<=n;i++) a[i]=b[i]=Read();
for(int i=1;i<=2*n;i++ ) id[i]=(i-1)/block+1;
sort(b+1,b+1+n);
len=unique(b+1,b+1+n)-b-1;
for(int i=1;i<=n;i++) a[i]=lower_bound(b+1,b+1+len,a[i])-b;
for(int i=1;i<n;i++)
{
int x=Read(),y=Read();
add(x,y),add(y,x);
}
dfs(1,0);
// for(int i=1;i<=index_;i++) cout<<ref[i]<<" ";
for(int i=1;i<=m;i++)
{
int x=Read(),y=Read();
int z=lca(x,y);
if(z==x||z==y)
{
if(idx[x]>idx[y]) swap(x,y);
q[i].l=idx[x],q[i].r=idx[y];
}
else
{
if(idx[x]>Idx[y]) swap(x,y);
q[i].l=Idx[x],q[i].r=idx[y];
q[i].zx=z;
}
q[i].id=i;
}
sort(q+1,q+1+m,comp);
CaptainMo();
for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
return 0;
}
该去做练习了啊…