莫队是一个优美的暴力
当我们可以在 O(1) 时间推到 L[i-1], L[i+1] , R[i-1] , R[i+1] 的时候 ,就可以使用莫队来解决这个问题啦
一 普通莫队
首先分块,将询问的区间按照左端点所处的块来排序,然后按顺序处理每一个询问就好了
这里有一个小 trick ,排序的时候可以按照块的奇偶性来排序,可以大大的提高效率,虽然我并不知道为什么。
经典例题: 小Z的袜子
#include<cstdio> #include<cstring> #include<algorithm> #include<iostream> #include<cmath> #include<stack> #include<queue> using namespace std; typedef long long ll; const int maxn = 100010; int n,m,blo; int l=1,r=0; int c[maxn],cnt[maxn],pos[maxn]; ll res=0; struct Q{ int l,r,id; }q[maxn]; struct Ans{ ll a,b; }ans[maxn]; bool cmp(Q a,Q b){ if(pos[a.l]==pos[b.l]) return a.r<b.r; return a.l<b.l; } void add(int i){ res-=1ll*cnt[c[i]]*cnt[c[i]]; ++cnt[c[i]]; res+=1ll*cnt[c[i]]*cnt[c[i]]; } void del(int i){ res-=1ll*cnt[c[i]]*cnt[c[i]]; --cnt[c[i]]; res+=1ll*cnt[c[i]]*cnt[c[i]]; } ll gcd(ll a,ll b){ if(a<b) swap(a,b); return b==0?a:gcd(b,a%b); } ll read(){ ll s=0,f=1; char ch=getchar(); while(ch<'0' || ch>'9'){ if(ch=='-') f=-1; ch=getchar(); } while(ch>='0' && ch<='9'){ s=s*10+ch-'0'; ch=getchar(); } return s*f; } int main(){ n=read(),m=read(); blo=sqrt(n); for(int i=1;i<=n;i++) c[i]=read(),pos[i]=(i-1)/blo+1; 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,cmp); for(int i=1;i<=m;i++){ while(l<q[i].l){ del(l); l++; } while(l>q[i].l){ add(l-1); l--; } while(r<q[i].r){ add(r+1); r++; } while(r>q[i].r){ del(r); r--; } if(q[i].l==q[i].r){ ans[q[i].id]=(Ans){0,1}; continue; } ans[q[i].id]=(Ans){res-(r-l+1),1ll*(r-l+1)*(r-l)}; } for(int i=1;i<=m;i++){ ll g=gcd(ans[i].a,ans[i].b); printf("%lld/%lld\n",ans[i].a/g,ans[i].b/g); } return 0; }
二 带修改莫队
带修改莫队是在普通莫队的基础上增加了修改操作
为保证正确性,在询问之前,所有在该次询问时间点之前的修改操作都要修改完 ,之后的修改操作都不能修改
所以带修莫队要比普通莫队的询问多维护一个时间节点
那么怎么维护答案呢?
每次修改答案的时候,如果当前修改的时间点(head)大于询问时间点(q[i].t)那么就将修改还原, 否则继续修改
剩下的就与普通莫队一样了
经典例题: 数颜色
#include<cstdio> #include<cstring> #include<algorithm> #include<iostream> #include<cmath> #include<stack> #include<queue> using namespace std; typedef long long ll; const int maxn = 100010; int n,m,blo,cnt1,cnt2; int c[maxn],cnt[1000010],pos[maxn],last[maxn],ans[maxn]; int L=1,R=0,res,head=0; struct C{ int x,y,last; }md[maxn]; struct Q{ int l,r,id,t; }q[maxn]; bool cmp(Q a,Q b){ if(pos[a.l]==pos[b.l]){ if(pos[a.r]==pos[b.r]) return a.t<b.t; return pos[a.r]<pos[b.r]; } return pos[a.l]<pos[b.l]; } void change(int x,int col){ if(L<=x&&x<=R){ cnt[c[x]]--; if(cnt[c[x]]==0) res--; c[x]=col; if(cnt[c[x]]==0) res++; cnt[c[x]]++; }else c[x]=col; } void add(int x){ if(cnt[c[x]]==0) ++res; ++cnt[c[x]]; } void del(int x){ --cnt[c[x]]; if(cnt[c[x]]==0) --res; } inline int getb(int x){ return (x-1)/blo+1; } ll read(){ ll s=0,f=1; char ch=getchar(); while(ch<'0' || ch>'9'){ if(ch=='-') f=-1; ch=getchar(); } while(ch>='0' && ch<='9'){ s=s*10+ch-'0'; ch=getchar(); } return s*f; } int main(){ n=read(),m=read(); blo=sqrt(n)*10; for(int i=1;i<=n;i++) c[i]=read(),pos[i]=(i-1)/blo+1,last[i]=c[i]; char op[10]; for(int i=1;i<=m;i++){ scanf("%s",op); if(op[0]=='R'){ md[++cnt1].x=read(); md[cnt1].y=read(); md[cnt1].last=last[md[cnt1].x]; last[md[cnt1].x]=md[cnt1].y; }else{ q[++cnt2].l=read(),q[cnt2].r=read(),q[cnt2].id=cnt2; q[cnt2].t=cnt1; } } sort(q+1,q+1+cnt2,cmp); for(int i=1;i<=cnt2;i++){ while(head>q[i].t){ change(md[head].x,md[head].last); head--; } while(head<q[i].t){ head++; change(md[head].x,md[head].y); } while(L<q[i].l){ del(L); L++; } while(L>q[i].l){ add(L-1); L--; } while(R<q[i].r){ add(R+1); R++; } while(R>q[i].r){ del(R); R--; } ans[q[i].id]=res; } for(int i=1;i<=cnt2;i++) printf("%d\n",ans[i]); return 0; }
三 树上莫队
树上的莫队当然还是分块啦
怎么在树上分块呢? 参考一下王室联邦的树分块
分块代码:
·
int dfs(int u,int par){ // 树分块 int co=1; sta[++top]=u; dfn[u]=++tot; fa[u][0]=par; dep[u]=dep[par]+1; for(int i=1;i<=20;i++){ fa[u][i]=fa[fa[u][i-1]][i-1]; } for(int i=h[u];i!=-1;i=e[i].next){ int v=e[i].to; if(v==par) continue; co+=dfs(v,u); if(co>=blo){ ++cnt; while(co){ kuai[sta[top--]]=cnt; co--; } co=0; } } return co; }
然后就是将树上问题转变成序列问题 -- dfs 序
接下来的套路与普通的带修改莫队就是一样的了
每次对答案的维护直接暴力就好
void change(int x,int y){ // 暴力更新答案 while(x!=y){ if(dep[x]>dep[y]) update(x),x=fa[x][0]; else update(y),y=fa[y][0]; } }
要注意处理 lca 的答案
做完了~
// luogu-judger-enable-o2 // 糖果公园 #include<cstdio> #include<cstring> #include<algorithm> #include<iostream> #include<cmath> using namespace std; typedef long long ll; const int maxn = 100010; int n,m,q,blo,tot,cnt1,cnt2; int v[maxn],w[maxn],c[maxn],last[maxn],kuai[maxn],vis[maxn],ci[maxn]; int head=0; ll res=0,ans[maxn]; int h[maxn],size; struct E{ int to,next; }e[maxn<<1]; void add(int u,int v){ e[++size].to=v; e[size].next=h[u]; h[u]=size; } struct C{ int x,y,last; }rc[maxn]; struct Q{ int x,y,bx,by,tim,id; }rq[maxn]; bool cmp(Q a,Q b){ if(a.bx==b.bx){ if(a.by==b.by) return a.tim<b.tim; return a.by<b.by; } return a.bx<b.bx; } int sta[maxn],top; int dep[maxn],fa[maxn][25],dfn[maxn],cnt; // cnt 块的数量 dfn 括号序列 int dfs(int u,int par){ // 树分块 int co=1; sta[++top]=u; dfn[u]=++tot; fa[u][0]=par; dep[u]=dep[par]+1; for(int i=1;i<=20;i++){ fa[u][i]=fa[fa[u][i-1]][i-1]; } for(int i=h[u];i!=-1;i=e[i].next){ int v=e[i].to; if(v==par) continue; co+=dfs(v,u); if(co>=blo){ ++cnt; while(co){ kuai[sta[top--]]=cnt; co--; } co=0; } } return co; } int LCA(int u,int v){ // 树上莫队要注意对 LCA 的处理 if(dep[u]<dep[v]) swap(u,v); for(int j=20;j>=0;j--){ if(dep[fa[u][j]]>=dep[v]) u=fa[u][j]; } if(u==v) return u; for(int j=20;j>=0;j--){ if(fa[u][j]!=fa[v][j]){ u=fa[u][j],v=fa[v][j]; } }return fa[u][0]; } void update(int x){ // 从答案中消除这个点 if(vis[x]){ vis[x]=0; // 标为没有计入答案 res-=1ll*w[ci[c[x]]]*v[c[x]]; --ci[c[x]]; }else{ vis[x]=1; ++ci[c[x]]; res+=1ll*w[ci[c[x]]]*v[c[x]]; } } void change(int x,int y){ // 暴力更新答案 while(x!=y){ if(dep[x]>dep[y]) update(x),x=fa[x][0]; else update(y),y=fa[y][0]; } } void modify(int x,int C){ if(!vis[x]) c[x]=C; else{ update(x); c[x]=C; update(x); } } int main(){ memset(h,-1,sizeof(h)); scanf("%d%d%d",&n,&m,&q); blo=(int)pow(n,0.60); // printf("%d\n",blo); for(int i=1;i<=m;i++) scanf("%d",&v[i]); for(int i=1;i<=n;i++) scanf("%d",&w[i]); int u,v; for(int i=1;i<n;i++){ scanf("%d%d",&u,&v); add(u,v),add(v,u); } for(int i=1;i<=n;i++){ scanf("%d",&c[i]); last[i]=c[i]; } dfs(1,0); int op,x,y; for(int i=1;i<=q;i++){ scanf("%d%d%d",&op,&x,&y); if(op==0){ rc[++cnt1].x=x,rc[cnt1].y=y,rc[cnt1].last=last[x],last[x]=y; }else{ if(dfn[x]>dfn[y]) swap(x,y); // 括号序列中的顺序 rq[++cnt2].x=x,rq[cnt2].y=y,rq[cnt2].tim=cnt1,rq[cnt2].id=cnt2; rq[cnt2].bx=kuai[x],rq[cnt2].by=kuai[y]; } } sort(rq+1,rq+1+cnt2,cmp); int lca=LCA(rq[1].x,rq[1].y); while(head<rq[1].tim) ++head,modify(rc[head].x,rc[head].y); change(rq[1].x,rq[1].y); update(lca); ans[rq[1].id]=res; update(lca); for(int i=2;i<=cnt2;i++){ while(head>rq[i].tim) modify(rc[head].x,rc[head].last),--head; while(head<rq[i].tim) ++head,modify(rc[head].x,rc[head].y); change(rq[i-1].x,rq[i].x); change(rq[i-1].y,rq[i].y); lca=LCA(rq[i].x,rq[i].y); update(lca); ans[rq[i].id]=res; update(lca); } for(int i=1;i<=cnt2;i++){ printf("%lld\n",ans[i]); }printf("\n"); return 0; }