莫队
莫队
首先对序列进行分块,每个块大小为block
莫队安排的顺序是,首先按照L端点所属的块序号排序,对于L在同一组的块,对R端点单调递增。
因为L被分了组,所以L端点移动的复杂度不会大于block
那R端点呢,因为R端点在每组内移动都是单调的,所以总移动次数为,均摊到M个查询上就是,近似令N=M
则复杂度为
根据复杂度瓶颈的定义,该算法的复杂度瓶颈为
当且仅当时,复杂度最小。
总时间复杂度为
应用
1.小Z的袜子
考虑加入和删除一个数的影响
原来是这个颜色cnt个,有cnt * (cnt-1)=cnt * cnt-cnt个贡献
那么cnt+1的影响为(cnt+1) * cnt=cnt * cnt+cnt
那么加入一个数产生额外贡献为2 * cnt
删除一个数同理
点击查看代码
#include<functional>
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<complex>
#include<string>
#include<cstdio>
#include<vector>
#include<cmath>
#include<queue>
#include<deque>
#include<stack>
#include<map>
#include<set>
#define ll long long
#define pa pair<ll,int>
using namespace std;
const int maxn=5e5+101;
const int MOD=998244353;
const int inf=2147483647;
const double pi=acos(-1);
const double eps=1e-12;
int read(){
int x=0,f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
return x*f;
}
int n,m,lens;
int c[maxn],cnt[maxn],id[maxn];
struct wzq{
int l,r,pos,ans;
}q[maxn];
int gcd(ll a,ll b){
if(b>a)swap(a,b);
if(b==0)return a;
return gcd(b,a%b);
}
int main(){
n=read();m=read();
lens=sqrt(n+0.5); //精度问题
for(int i=1;i<=n;i++)c[i]=read(),id[i]=(i-1)/lens;
for(int i=1;i<=m;i++){
q[i].l=read();q[i].r=read();q[i].pos=i;
}
sort(q+1,q+m+1,[](wzq i,wzq j){
if(id[i.l]==id[j.l])return i.r<j.r;
return id[i.l]<id[j.l];
});
int l=1,r=0,ans=0;
for(int i=1;i<=m;i++){
while(r<q[i].r){ans+=cnt[c[++r]]*2;cnt[c[r]]++;}
while(r>q[i].r){ans+=2-cnt[c[r]]*2;cnt[c[r--]]--;}
while(l<q[i].l){ans+=2-cnt[c[l]]*2;cnt[c[l++]]--;}
while(l>q[i].l){ans+=cnt[c[--l]]*2;cnt[c[l]]++;}
q[i].ans=ans;
}
sort(q+1,q+m+1,[](wzq i,wzq j){
return i.pos<j.pos;
});
for(int i=1;i<=m;i++){
ll now=(ll)(q[i].r-q[i].l+1)*(ll)(q[i].r-q[i].l);
int d=gcd(q[i].ans,now);
if(q[i].ans==0)puts("0/1");
else printf("%d/%lld\n",q[i].ans/d,now/d);
}
return 0;
}
带修莫队
带修莫队就是一种支持单点修改的莫队算法。
还是对询问进行排序,每个询问除了左端点和右端点还要记录这次询问是在第几次修改之后(时间),以左端点所在块为第一关键字,以右端点所在块为第二关键字,以时间为第三关键字进行排序。
暴力查询时,如果当前修改数比询问的修改数少就把没修改的进行修改,反之回退。
需要注意的是,修改分为两部分:
若修改的位置在当前区间内,需要更新答案(del原颜色,add修改后的颜色)。
无论修改的位置是否在当前区间内,都要进行修改(以供add和del函数在以后更新答案)。
分块大小的选择以及复杂度证明
块大小取
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define pa pair<ll,int>
using namespace std;
const int maxn=1e4+101;
const int MOD=998244353;
const int inf=2147483647;
const double pi=acos(-1);
const double eps=1e-12;
int read(){
int x=0,f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
return x*f;
}
int n,m,a[maxn],id[maxn],ans;
int qcnt,mcnt;
struct ask{
int l,r;
int mcnt,id;
int ans;
}q[maxn];
struct modify{int pos,val;}p[maxn];
map<int,int>mm;
void add(int x){
++mm[x];
if(mm[x]==1)ans++;
}
void del(int x){
--mm[x];
if(mm[x]==0)ans--;
}
void modify(int x,int ti){
if(p[ti].pos>=q[x].l && p[ti].pos<=q[x].r){
del(a[p[ti].pos]);
add(p[ti].val);
//修改在区间范围内,则add或del
}
swap(a[p[ti].pos],p[ti].val);
//修改后交换颜色
return ;
}
int main(){
n=read();m=read();int block=pow(n,0.6666666);
for(int i=1;i<=n;i++){
a[i]=read();
id[i]=(i-1)/block;
}
for(int i=1;i<=m;i++){
char ch[2];cin>>ch;
int x=read(),y=read();
if(ch[0]=='Q'){
q[++qcnt].l=x;q[qcnt].r=y;
q[qcnt].mcnt=mcnt;q[qcnt].id=qcnt;
}
else p[++mcnt].pos=x,p[mcnt].val=y;
}
sort(q+1,q+qcnt+1,[](ask i,ask j){
if(id[i.l]==id[j.l]){
if(id[i.r]==id[j.r])return i.mcnt<j.mcnt;
return id[i.r]<id[j.r];
}
return id[i.l]<id[j.l];
});
int l=1,r=0,t=0;
for(int i=1;i<=qcnt;i++){
while(r<q[i].r)add(a[++r]);
while(r>q[i].r)del(a[r--]);
while(l<q[i].l)del(a[l++]);
while(l>q[i].l)add(a[--l]);
while(t<q[i].mcnt)modify(i,++t);
while(t>q[i].mcnt)modify(i,t--);
q[i].ans=ans;
}
sort(q+1,q+qcnt+1,[](ask i,ask j){return i.id<j.id;});
for(int i=1;i<=qcnt;i++)printf("%d\n",q[i].ans);
return 0;
}
树上莫队
普通树上莫队
考虑树上如何分块?
可以dfs+时间戳
将dfs化为时间戳
从1开始DFS,得到的时间戳是:in=[1,2,8,3,5],out=[10,7,9,4,6],标记顺序是[1,2,4,4,5,5,2,3,3,1]
假设u深度<v深度,即in[u]<in[v]
当v在u的子树里面,我们就可以询问[in[u],in[v]]区间,否则询问[out[u],in[v]]
一个询问[l,r]表示询问所有 l到r这一段标记里,恰好出现了一次的节点的答案
这样做有没有问题呢?
显然是有问题的,当u和v不是祖宗与后代的关系,即lca(u,v)!=u(因为我们假设了in[u]<in[v])时,lca(u,v)没有在[out[u],in[v]]出现,所以要单独加上lca的贡献
那怎么处理区间里只出现一次的数呢,因为如果是我们需要的路径上的数它会出现一次,不是的话它只会出现两次,我们想到了异或。
没错,如果碰到了vis[pos]==1的话说明碰到过一次了,这是第二次,所以要减去相应数值,否则加入数值,最后vis[pos]异或一下
大体思路应该知道了,边看程序边理一遍
树上莫队模板题
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define pa pair<ll,int>
using namespace std;
const int maxn=1e6+101;
const int MOD=998244353;
const int inf=2147483647;
const double pi=acos(-1);
const double eps=1e-12;
int read(){
int x=0,f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
return x*f;
}
int n,m,col[maxn],bl[maxn];
int tot,head[maxn],to[maxn],nx[maxn];
int dep[maxn],fa[maxn][21],in[maxn],out[maxn],id[maxn],idx;
void add(int x,int y){to[++tot]=y;nx[tot]=head[x];head[x]=tot;}
void dfs(int x,int f){
dep[x]=dep[f]+1;fa[x][0]=f;
in[x]=++idx;
id[idx]=x;
for(int i=1;i<=20;i++)fa[x][i]=fa[fa[x][i-1]][i-1];
for(int i=head[x];i;i=nx[i]){
int v=to[i];if(v==f)continue;
dfs(v,x);
}
out[x]=++idx;
id[idx]=x;
return ;
}
int lca(int x,int y){
if(dep[x]<dep[y])swap(x,y);
for(int i=20;i>=0;i--){
if(dep[fa[x][i]]>=dep[y])x=fa[x][i];
}
if(x==y)return x;
for(int i=20;i>=0;i--){
if(fa[x][i]!=fa[y][i]){
x=fa[x][i];
y=fa[y][i];
}
}
return fa[x][0];
}
struct ask{
int l,r;
int ans,id;
}q[maxn];
int ans,vis[maxn];
map<int,int>mm;
void revise(int pos){
if(vis[pos]){
mm[col[pos]]--;
if(mm[col[pos]]==0)ans--;
}
else {
mm[col[pos]]++;
if(mm[col[pos]]==1)ans++;
}
vis[pos]^=1;
return ;
}
int main(){
n=read();int block=sqrt(n+0.5);
for(int i=1;i<=n;i++)col[i]=read();
for(int i=1;i<n;i++){
int x=read(),y=read();
add(x,y);add(y,x);
}
dfs(1,1);
for(int i=1;i<=idx;i++)bl[i]=(i-1)/block;
m=read();
for(int i=1;i<=m;i++){
int x=read(),y=read();q[i].id=i;
if(in[x]>in[y])swap(x,y);
q[i].r=in[y];
q[i].l=(lca(x,y)==x)?in[x]:out[x];
}
sort(q+1,q+m+1,[](ask i,ask j){
if(bl[i.l]==bl[j.l])return i.r<j.r;
return bl[i.l]<bl[j.l];
});
int l=1,r=0;
for(int i=1;i<=m;i++){
while(r<q[i].r)revise(id[++r]);
while(r>q[i].r)revise(id[r--]);
while(l<q[i].l)revise(id[l++]);
while(l>q[i].l)revise(id[--l]);
int x=id[l],y=id[r],ff=lca(x,y);
if(x!=ff && y!=ff){
revise(ff);
q[i].ans=ans;
revise(ff);
//lca不在2节点dfs序之间出现,需要额外讨论
//算完还要revise回去,因为lca不在两点dfs序之间出现,不revise回去会影响后面计算
}
else q[i].ans=ans;
}
sort(q+1,q+m+1,[](ask i,ask j){return i.id<j.id;});
for(int i=1;i<=m;i++)printf("%d\n",q[i].ans);
return 0;
}
树上带修莫队
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define pa pair<ll,int>
using namespace std;
const int maxn=2e5+101;
const int MOD=998244353;
const int inf=2147483647;
const double pi=acos(-1);
const double eps=1e-12;
int read(){
int x=0,f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
return x*f;
}
int n,m,q,v[maxn],w[maxn],c[maxn],bl[maxn];
int tot,head[maxn],to[maxn],nx[maxn];
void add(int x,int y){to[++tot]=y;nx[tot]=head[x];head[x]=tot;}
int dep[maxn],f[maxn][21],in[maxn],out[maxn],idx,id[maxn];
void dfs(int x,int fa){
dep[x]=dep[fa]+1;f[x][0]=fa;
in[x]=++idx;id[idx]=x;
for(int i=1;i<19;i++)f[x][i]=f[f[x][i-1]][i-1];
for(int i=head[x];i;i=nx[i]){
int y=to[i];if(y==fa)continue;
dfs(y,x);
}
out[x]=++idx;id[idx]=x;
return;
}
int lca(int x,int y){
if(dep[x]<dep[y])swap(x,y);
for(int i=18;i>=0;i--){
if(dep[f[x][i]]>=dep[y])x=f[x][i];
}
if(x==y)return x;
for(int i=18;i>=0;i--){
if(f[x][i]!=f[y][i]){
x=f[x][i];
y=f[y][i];
}
}
return f[x][0];
}
int mcnt,qcnt;
struct ask{
int x,y,t;
int id;
ll ans;
}qq[maxn];
struct modify{int x,y;}p[maxn];
int vis[maxn];
ll ans;
int mm[maxn];
//尽量不用map来存个数,会超时
void revise(int pos){
if(vis[pos]){
ans-=(ll)w[mm[c[pos]]]*(ll)v[c[pos]];
mm[c[pos]]--;
}
else {
mm[c[pos]]++;
ans+=(ll)w[mm[c[pos]]]*(ll)v[c[pos]];
}
vis[pos]^=1;
return ;
}
void Modi(int t){
if(vis[p[t].x]){
revise(p[t].x);
swap(p[t].y,c[p[t].x]);
revise(p[t].x);
}
else swap(p[t].y,c[p[t].x]);
return ;
}
int main(){
n=read();m=read();q=read();int block=pow(n,0.6666666);
for(int i=1;i<=m;i++)v[i]=read();
for(int i=1;i<=n;i++)w[i]=read();
for(int i=1;i<n;i++){
int x=read(),y=read();
add(x,y);add(y,x);
}
for(int i=1;i<=n;i++)c[i]=read();
dfs(1,0);
for(int i=1;i<=idx;i++)bl[i]=(i-1)/block;
for(int i=1;i<=q;i++){
int type=read(),x=read(),y=read();
if(type==0){
p[++mcnt].x=x;
p[mcnt].y=y;
}
else {
if(in[x]>in[y])swap(x,y);
qq[++qcnt].x=(lca(x,y)==x)?in[x]:out[x];
qq[qcnt].y=in[y];
qq[qcnt].id=qcnt;
qq[qcnt].t=mcnt;
}
}
sort(qq+1,qq+qcnt+1,[](ask i,ask j){
if(bl[i.x]==bl[j.x]){
if(bl[i.y]==bl[j.y])return i.t<j.t;
return bl[i.y]<bl[j.y];
}
return bl[i.x]<bl[j.x];
});
int l=1,r=0,t=0;
for(int i=1;i<=qcnt;i++){
while(t<qq[i].t)Modi(++t);
while(t>qq[i].t)Modi(t--);
while(r<qq[i].y)revise(id[++r]);
while(r>qq[i].y)revise(id[r--]);
while(l<qq[i].x)revise(id[l++]);
while(l>qq[i].x)revise(id[--l]);
int x=id[l],y=id[r],ff=lca(x,y);
if(x!=ff && y!=ff){
revise(ff);
qq[i].ans=ans;
revise(ff);
}
else qq[i].ans=ans;
}
sort(qq+1,qq+qcnt+1,[](ask i,ask j){return i.id<j.id;});
for(int i=1;i<=qcnt;i++)printf("%lld\n",qq[i].ans);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】