莫队 基础篇
普通莫队
DQUERY - D-query
先想一下最朴素的暴力怎么写。显然可以用一个
然后考虑如果如何用一个已经求出来答案的询问推出另外一个询问的答案。
显然我们要增加一部分数和删掉一部分数。对于删掉的数如果其出现次数为
然后莫队就是,在上面那种一个一个移动指针的情况下求答案。但是要预先知道所有询问,把所有询问离线下来,然后排序。
所以,莫队是一种离线算法。
现在,我们将长度为
事实上是非常好理解的,所以直接看一下代码:
#include<bits/stdc++.h>
#define int long long
#define N 50005
#define M 200005
#define S 1000005
using namespace std;
int n,m,len,w[N],ans[M],cnt[S];
struct node{
int id,l,r;
}q[M];
int get(int x){
return x/len;
}
bool cmp(const node &a,const node &b){
int i=get(a.l),j=get(b.l);
if(i!=j)return i<j;
return a.r<b.r;
}
void add(int x,int &res){
if(!cnt[x])res++;
cnt[x]++;
}
void del(int x,int &res){
cnt[x]--;
if(!cnt[x])res--;
}
signed main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>w[i];
}
cin>>m;
len=max(1ll,(int)sqrt(n*n/m));
for(int i=0;i<m;i++){
int l,r;
cin>>l>>r;
q[i]={i,l,r};
}
sort(q,q+m,cmp);
for(int i=0,j=1,k=0,res=0;k<m;k++){
int id=q[k].id,l=q[k].l,r=q[k].r;
while(i<r)add(w[++i],res);
while(i>r)del(w[i--],res);
while(j<l)del(w[j++],res);
while(j>l)add(w[--j],res);
ans[id]=res;
}
for(int i=0;i<m;i++){
cout<<ans[i]<<'\n';
}
return 0;
}
代码是比较好理解的,不过注意四个移动指针的循环的顺序是有要求的,故不要随意更改顺序。
然后总结一下莫队的本质,就是在暴力的基础上把询问排个序,然后要求如果你知道询问
带修莫队
数颜色 / 维护队列
这里先说一下,带修莫队的块长一般使用
带修莫队的差别和普通莫队事实上并不大,就是要加一个维度记录时间。
事实上,原来的四种转移方式再加上
然后想一下事件不同怎么转移。大概就是判断一下当前这个修改的位置在不在我们的询问内,如果在就把要改的东西加上,原来的东西减掉。然后这是对询问的贡献,但是修改是事实存在的,所以我们交换一下修改前和修改后的东西,因为后面有可能要再换回来,这样相当于是记录了一下。
然后就可以直接看一下代码了:
#include<bits/stdc++.h>
#define int long long
#define N 1500050
using namespace std;
int n,m,x,y,lim,a[N],cntq,cntr,ll,rr,mp[N],cu,ans[N];
char c;
struct node{
int l,r,t,id;
}q[N];
struct edge{
int l,r;
}r[N];
bool cmp(node a,node b){
if(a.l/lim!=b.l/lim)return a.l/lim>b.l/lim;
else if(a.r/lim!=b.r/lim)return a.r/lim>b.r/lim;
else return a.t>b.t;
}
void add(int x){
if(!mp[x])cu++;mp[x]++;
}
void del(int x){
mp[x]--;if(!mp[x])cu--;
}
signed main(){
cin>>n>>m;
lim=n/cbrt(m);
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1;i<=m;i++){
cin>>c>>ll>>rr;
if(c=='Q'){
q[++cntq]={ll,rr,cntr,cntq};
}
else{
r[++cntr]={ll,rr};
}
}
sort(q+1,q+cntq+1,cmp);
int L=1,R=0,las=0;
for(int i=1;i<=cntq;i++){
while(R<q[i].r)add(a[++R]);
while(R>q[i].r)del(a[R--]);
while(L>q[i].l)add(a[--L]);
while(L<q[i].l)del(a[L++]);
while(las<q[i].t){
las++;
if(r[las].l>=L&&r[las].l<=R)add(r[las].r),del(a[r[las].l]);
swap(a[r[las].l],r[las].r);
}
while(las>q[i].t){
if(r[las].l>=L&&r[las].l<=R)add(r[las].r),del(a[r[las].l]);
swap(a[r[las].l],r[las].r);
las--;
}
ans[q[i].id]=cu;
}
for(int i=1;i<=cntq;i++)cout<<ans[i]<<'\n';
return 0;
}
树上莫队
Count on a tree II
首先如果只有子树查询,只需要用每个点的
但是这种方法并不适用于链查询。对于链查询,我们可以使用欧拉序。
具体地,我们设
对于查询
代码:
#include<bits/stdc++.h>
#define int long long
#define N 200005
#define pii pair<int,int>
#define x first
#define y second
#define mod 1000000007
#define pct __builtin_popcount
#define inf 2e18
#define eps 1e-10
using namespace std;
int T=1,n,m,cnt,len,col[N],b[N],bel[N],res[N];
int h[N],e[N],ne[N],idx,ans;
int fir[N],las[N],id[N],tot;
int dep[N],fa[N],siz[N],son[N],top[N],c[N];
bool st[N];
struct qry{
int l,r,p,id;
bool operator<(const qry &t)const{
if(bel[l]!=bel[t.l])return bel[l]<bel[t.l];
return r<t.r;
}
}q[N];
void add(int a,int b){
e[idx]=b;ne[idx]=h[a];h[a]=idx++;
}
void dfs1(int u,int f){
dep[u]=dep[f]+1;
fa[u]=f;
siz[u]=1;
fir[u]=++tot;
id[tot]=u;
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
if(j==fa[u])continue;
dfs1(j,u);
siz[u]+=siz[j];
if(siz[j]>siz[son[u]])son[u]=j;
}
las[u]=++tot;
id[tot]=u;
}
void dfs2(int u,int f){
top[u]=f;
if(son[u])dfs2(son[u],f);
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
if(j==fa[u]||j==son[u])continue;
dfs2(j,j);
}
}
int get_lca(int a,int b){
while(top[a]!=top[b]){
if(dep[top[a]]<dep[top[b]])swap(a,b);
a=fa[top[a]];
}
return dep[a]<dep[b]?a:b;
}
void modify(int x){
if(st[x]){
c[col[x]]--;
if(!c[col[x]])ans--;
}
else{
if(!c[col[x]])ans++;
c[col[x]]++;
}
st[x]^=1;
}
void solve(int cs){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>col[i];
b[i]=col[i];
}
sort(b+1,b+n+1);
cnt=unique(b+1,b+n+1)-b-1;
for(int i=1;i<=n;i++){
col[i]=lower_bound(b+1,b+cnt+1,col[i])-b;
}
memset(h,-1,sizeof h);
for(int i=1;i<n;i++){
int a,b;
cin>>a>>b;
add(a,b);add(b,a);
}
dfs1(1,0);
dfs2(1,1);
len=sqrt(tot);
for(int i=1;i<=tot;i++){
bel[i]=(i-1)/len+1;
}
for(int i=1;i<=m;i++){
int l,r,p;
cin>>l>>r;
if(fir[l]>fir[r])swap(l,r);
p=get_lca(l,r);
if(l==p){
q[i].l=fir[l];
q[i].r=fir[r];
}
else{
q[i].l=las[l];
q[i].r=fir[r];
q[i].p=p;
}
q[i].id=i;
}
sort(q+1,q+m+1);
int l=1,r=0;
for(int i=1;i<=m;i++){
while(l>q[i].l)modify(id[--l]);
while(r<q[i].r)modify(id[++r]);
while(l<q[i].l)modify(id[l++]);
while(r>q[i].r)modify(id[r--]);
if(q[i].p)modify(q[i].p);
res[q[i].id]=ans;
if(q[i].p)modify(q[i].p);
}
for(int i=1;i<=m;i++){
cout<<res[i]<<'\n';
}
}
void solution(){
/*
nothing here
*/
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
// srand(time(0));
// cin>>T;
for(int cs=1;cs<=T;cs++){
solve(cs);
}
return 0;
}
综合题
糖果公园
树上带修莫队模板题。考虑按照上文所述的套路,先把树拍到欧拉序序列上,然后直接做带修莫队。
代码:
#include<bits/stdc++.h>
#define int long long
#define N 202005
#define pii pair<int,int>
#define x first
#define y second
#define mod 1000000007
#define pct __builtin_popcount
#define inf 2e18
#define eps 1e-10
using namespace std;
int T=1,n,m,Q,len,cnt,v[N],w[N],val[N],bel[N],res[N];
int dep[N],fa[N],siz[N],son[N],top[N];
int tot,fir[N],las[N],id[N],c[N],ans;
bool st[N];
vector<int>e[N];
struct node{
int p,v;
}p[N];
struct qry{
int l,r,p,t,id;
bool operator<(const qry &u)const{
if(bel[l]!=bel[u.l])return bel[l]<bel[u.l];
if(r!=u.r)return r<u.r;
return t<u.t;
}
}q[N];
void add(int a,int b){
e[a].push_back(b);
}
void dfs1(int u,int f){
dep[u]=dep[f]+1;
fa[u]=f;
siz[u]=1;
fir[u]=++tot;
id[tot]=u;
for(auto j:e[u]){
if(j==fa[u])continue;
dfs1(j,u);
siz[u]+=siz[j];
if(siz[j]>siz[son[u]])son[u]=j;
}
las[u]=++tot;
id[tot]=u;
}
void dfs2(int u,int f){
top[u]=f;
if(son[u])dfs2(son[u],f);
for(auto j:e[u]){
if(j==fa[u]||j==son[u])continue;
dfs2(j,j);
}
}
int get_lca(int a,int b){
while(top[a]!=top[b]){
if(dep[top[a]]<dep[top[b]])swap(a,b);
a=fa[top[a]];
}
return dep[a]<dep[b]?a:b;
}
void add(int p){
c[val[p]]++;
ans+=v[val[p]]*w[c[val[p]]];
}
void del(int p){
ans-=v[val[p]]*w[c[val[p]]];
c[val[p]]--;
}
void move(int p){
if(st[p])del(p);
else add(p);
st[p]^=1;
}
void modify(int x){
if(st[p[x].p]){
move(p[x].p);
swap(val[p[x].p],p[x].v);
move(p[x].p);
}
else swap(val[p[x].p],p[x].v);
}
void solve(int cs){
cin>>n>>m>>Q;
for(int i=1;i<=m;i++){
cin>>v[i];
}
for(int i=1;i<=n;i++){
cin>>w[i];
}
for(int i=1;i<n;i++){
int a,b;
cin>>a>>b;
add(a,b);add(b,a);
}
for(int i=1;i<=n;i++){
cin>>val[i];
}
dfs1(1,0);
dfs2(1,1);
len=cbrt(tot)*cbrt(tot);
cnt=ceil(1.0*tot/len);
for(int i=1;i<=cnt;i++){
for(int j=(i-1)*len+1;j<=i*len;j++){
bel[j]=i;
}
}
int cntp=0,cntq=0;
for(int i=1;i<=Q;i++){
int op,l,r,P;
cin>>op>>l>>r;
if(op){
P=get_lca(l,r);
cntq++;
q[cntq].t=cntp;
q[cntq].id=cntq;
if(fir[l]>fir[r])swap(l,r);
if(l==P){
q[cntq].l=fir[l];
q[cntq].r=fir[r];
}
else{
q[cntq].l=las[l];
q[cntq].r=fir[r];
q[cntq].p=P;
}
}
else{
cntp++;
p[cntp].p=l;
p[cntp].v=r;
}
}
sort(q+1,q+cntq+1);
int l=1,r=0,t=0;
for(int i=1;i<=cntq;i++){
while(l>q[i].l)move(id[--l]);
while(r<q[i].r)move(id[++r]);
while(l<q[i].l)move(id[l++]);
while(r>q[i].r)move(id[r--]);
while(t<q[i].t)modify(++t);
while(t>q[i].t)modify(t--);
if(q[i].p)move(q[i].p);
res[q[i].id]=ans;
if(q[i].p)move(q[i].p);
}
for(int i=1;i<=cntq;i++){
cout<<res[i]<<'\n';
}
}
void solution(){
/*
nothing here
*/
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
// srand(time(0));
// cin>>T;
for(int cs=1;cs<=T;cs++){
solve(cs);
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】