重修 分块和莫队
带修莫队
相信我会了,这里只考虑时间复杂度。
设我们块大小( 两轴块大小)为 ,则时间复杂度为 里面:
上柿为三维长方体 内 个点以 轴分别以 块长分块后用一根每一段与某坐标轴平行的折线串起来,线以分块策略下的最劣情况长度。
(由于我不会画 3D 的图,所以自行脑补 qwq)
我们若规定 ,则上述柿子变为
均衡一下得到 ,总复杂度为 。
为啥别的题解得到的 和我的不一样捏。
回滚莫队
分成两种:只增加、只删除。
只删除的例题:P8078 [WC2022] 秃子酋长
二次离线莫队
设莫队单次修改的时间为 ,则二次离线莫队将莫队从 优化到 ,当然前提是满足区间可减性。
给你一个序列 ,每次查询给一个区间 ,查询 ,且 的二元组 的个数. 是指按位异或。。
设 为 中 的个数,我们预处理这些数,最后时间复杂度也和 有关。
设 表示
的 的个数。
由于区间可减,得到
所以下文用 来简写 。
只有当 时 ,所以得到
我们先正常莫队(甚至可以奇偶优化)。举个例子(莫队区间设为 )。
当 增大至 时,此询问比上次减少
增大、 减小、 增大 同理,不再赘述。
我们可以用桶 预处理出 。所以剩下的只要拎出 类的东西求即可,最后再代回来算答案。
接下来就是二次离线部分了。
我们将每一个形如 的询问挂在 位置上。
然后再用桶扫,同时计算每个位置上的询问。
这部分是 , 是因为二级询问有 个,每个只要 回答。
然后就做完了,总时间 ,与开头说的相同。
例题代码:
点击查看代码
//Said no more counting dollars. We'll be counting stars. #include<bits/stdc++.h> using namespace std; #define pb emplace_back #define mem(x,y) memset(x,y,sizeof(x)) #define For(i,j,k) for(int i=j;i<=k;i++) #define Rof(i,j,k) for(int i=j;i>=k;i--) #define int long long #define N 100010 #define V 16384//值域 vector<int> bu;//0~V-1 popcnt=k 的数 int n,m,k,a[N],b[N],gap; struct Que{ int l,r,id,ans;//ans:比莫队中上次询问答案的增量 friend bool operator<(Que x,Que y){return b[x.l]==b[y.l]?((b[x.l]&1)?x.r<y.r:x.r>y.r):x.l<y.l;} }q[N]; int p[N];//ct((i+1)->[1,i]) ct表示贡献 int t[V];//桶 int ans[N];//最终答案 vector<tuple<int,int,int,int> > v[N]; //第一次莫队留下的询问v[i].<l,r,id,val>:[l,r] 区间中所有 x,sum ct(x->[1,i]),乘系数 val 贡献给询问 i signed main(){ scanf("%lld%lld%lld",&n,&m,&k); For(i,0,V-1) if(__builtin_popcount(i)==k) bu.pb(i); For(i,1,n) scanf("%lld",a+i); For(i,1,m) scanf("%lld%lld",&q[i].l,&q[i].r),q[i].id=i; gap=sqrt(n); For(i,1,n) b[i]=(i-1)/gap+1; sort(q+1,q+1+m); For(i,1,n){ for(int j:bu) t[a[i]^j]++; p[i]=t[a[i+1]]; } int L=1,R=0,l,r; For(i,1,m){ l=q[i].l,r=q[i].r; if(L<l) v[R].pb(L,l-1,i,-1); while(L<l){q[i].ans+=p[L-1]+(!k);++L;} if(R>r) v[L-1].pb(r+1,R,i,1); while(R>r){q[i].ans-=p[R-1]; --R;} if(L>l) v[R].pb(l,L-1,i,1); while(L>l){q[i].ans-=p[L-2]+(!k);--L;} if(R<r) v[L-1].pb(R+1,r,i,-1); while(R<r){q[i].ans+=p[R]; ++R;} } mem(t,0); int id,val,tmp; For(i,1,n){ for(int j:bu) t[a[i]^j]++; for(auto x:v[i]){ tie(l,r,id,val)=x; For(j,l,r){ tmp=t[a[j]]; q[id].ans+=tmp*val; } } } For(i,1,m) q[i].ans+=q[i-1].ans;//累计 For(i,1,m) ans[q[i].id]=q[i].ans;//重排 For(i,1,m) printf("%lld\n",ans[i]); return 0;}
树上莫队(fake)
指的是平摊成欧拉序后在序列上做莫队。
树上莫队(real)
首先先得会树分块。
为了保证最终莫队复杂度的正确性,我们需要做到:
-
属于同一块的节点之间的距离不大。
-
每个块中的节点不能太多也不能太少。
-
每个节点都要属于一个块。
-
编号相邻的块之间的距离不能太大。
我们让树分块后依次顺序编号就正好满足了第四个条件了。
分块后的排序方法:若路径 的 的时间戳大于 那么交换 。然后按照 所在块为第一关键字, 的时间戳为第二关键字排序。
注意这里有一个大坑点:
在指针移动的过程中,我们肯定是让移动前的位置和移动后的位置一起向 lca 靠近。然后利用 标记来
判断这个点是要进入区间还是出区间。
但是这个移动中会出现一个问题,移动时候如果跨过 lca 了会出问题,如下图。
我们按照上面步骤从 移到 的时候,两个 lca 都被标记了两次,也就是标记状态没有改变,这是错误的。
所以我们要把 lca 放到最后特判,单独更新。就解决问题了。
形象一点就是:因为若是边权这样处理没有问题,所以我们将树上路径点权移到它连向祖先的边上(这时候要删掉 lca 的点权),成功改为边权,然后正常移动,然后再变回点权(要把 lca 点权加回来)。
这甚至是树上带修莫队太毒瘤了。
代码被我吃了。由于是缝合题,代码有点长,但是很好理解。
点击查看代码
/* * Author: ShaoJia * Create Time: 2022-08-24 19:46:10 * Last Modified time: 2022-08-25 14:49:42 * Motto: We'll be counting stars. */ #include<bits/stdc++.h> using namespace std; #define fir first #define sec second #define mkp make_pair #define pb emplace_back #define For(i,j,k) for(int i=(j),i##_=(k);i<=i##_;i++) #define Rof(i,j,k) for(int i=(j),i##_=(k);i>=i##_;i--) #define ckmx(a,b) a=max(a,b) #define ckmn(a,b) a=min(a,b) #define debug(...) cerr<<"#"<<__LINE__<<": "<<__VA_ARGS__<<endl #define int long long char buf[1<<21],*p1,*p2; #define gc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++) inline 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<<3)+(x<<1)+(c^48);c=gc();} return x*f; } //------------------------------------------ const int N=100005,gap=2000,C=16; int b[N]; struct Que{ int u,v,t,id; friend bool operator<(Que x,Que y){ if(b[x.u]!=b[y.u]) return b[x.u]<b[y.u]; if(b[x.v]!=b[y.v]) return b[x.v]<b[y.v]; return x.t<y.t; } }q[N]; struct Chan{ int x,lst,nxt; }c[N]; vector<int> e[N]; int n,m,qt=0,v[N],w[N],a[N],s[N],st=0; int f[N][C+1],dep[N],bl=0,tim=0,now,ans[N],vis[N],cnt[N]; void dfs(int rt,int fa){ dep[rt]=dep[fa]+1; f[rt][0]=fa; For(i,1,C) f[rt][i]=f[f[rt][i-1]][i-1]; int tmp=st; s[++st]=rt; for(int i:e[rt]) if(i!=fa){ dfs(i,rt); if(st-tmp>gap){ bl++; while(st!=tmp) b[s[st--]]=bl; } } } int lca(int x,int y){ int xx,yy; if(dep[x]<dep[y]) swap(x,y); Rof(i,C,0){ xx=f[x][i]; if(dep[xx]>=dep[y]) x=xx; } if(x==y) return x; Rof(i,C,0){ xx=f[x][i]; yy=f[y][i]; if(xx!=yy) x=xx,y=yy; } return f[x][0]; } void del(int x){ now-=w[cnt[x]--]*v[x]; } void add(int x){ now+=w[++cnt[x]]*v[x]; } void work(int x){ if(vis[x]) del(a[x]); else add(a[x]); vis[x]^=1; } void change(int x,int val){ if(vis[x]) del(a[x]),add(val); a[x]=val; } void mov(int x,int y){ if(dep[x]<dep[y]) swap(x,y); while(dep[x]>dep[y]) work(x),x=f[x][0]; while(x!=y){ work(x),x=f[x][0], work(y),y=f[y][0]; } } signed main(){ int opt,x,y,tmp; n=read(),m=read(),tmp=read(); For(i,1,m) v[i]=read(); For(i,1,n) w[i]=read(); For(i,1,n-1){ x=read(),y=read(); e[x].pb(y); e[y].pb(x); } For(i,1,n) s[i]=a[i]=read(); while(tmp--){ opt=read(),x=read(),y=read(); if(!opt) c[++tim]=(Chan){x,s[x],y},s[x]=y; else qt++,q[qt]=(Que){x,y,tim,qt}; } dfs(1,0); if(st){ bl++; while(st) b[s[st--]]=bl; } sort(q+1,q+1+qt); int T=0,U=1,V=1; work(1); For(i,1,qt){ while(T<q[i].t) T++,change(c[T].x,c[T].nxt); while(T>q[i].t) change(c[T].x,c[T].lst),T--; work(lca(U,V)); mov(U,q[i].u); mov(V,q[i].v); work(lca(U=q[i].u,V=q[i].v)); ans[q[i].id]=now; } For(i,1,qt) printf("%lld\n",ans[i]); return 0;}
树上撒点
本文来自博客园,作者:ShaoJia,版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)