重修 分块和莫队

带修莫队

P1903 [国家集训队] 数颜色 / 维护队列

相信我会了,这里只考虑时间复杂度。

设我们块大小(l,r 两轴块大小)为 B,则时间复杂度为 O 里面:

m(nB)2+mB+n2B

上柿为三维长方体 x[1,n],y[1,n],z[1,m]m 个点以 x,y 轴分别以 B 块长分块后用一根每一段与某坐标轴平行的折线串起来,线以分块策略下的最劣情况长度。

(由于我不会画 3D 的图,所以自行脑补 qwq)

我们若规定 m>>B,则上述柿子变为

O(mn2B2+mB)

均衡一下得到 B=n2/3,总复杂度为 n2/3m

为啥别的题解得到的 B 和我的不一样捏。

回滚莫队

分成两种:只增加、只删除。

只删除的例题:P8078 [WC2022] 秃子酋长

二次离线莫队

设莫队单次修改的时间为 t,则二次离线莫队将莫队O(ntn) 优化到 O(nt+nn),当然前提是满足区间可减性

例题:P4887 【模板】莫队二次离线

给你一个序列 a,每次查询给一个区间 [l,r],查询 li<jr,且 popcnt(aiaj)=k 的二元组 (i,j) 的个数. 是指按位异或。n,q105,0ai,k<214

m[0,V)popcnt=k 的个数,我们预处理这些数,最后时间复杂度也和 m 有关。

f(x,l,r) 表示

y[l,r],popcnt(axay)=k

y 的个数。

由于区间可减,得到

f(x,l,r)=f(x,1,r)f(x,1,l1)

所以下文用 f(x,y) 来简写 f(x,1,y)

只有当 k=0popcnt(axax)=k,所以得到

f(x,x)=f(x,x1)+[k=0]

我们先正常莫队(甚至可以奇偶优化)。举个例子(莫队区间设为 [L,R])。

L 增大l 时,此询问比上次减少

x=Ll1f(x,x+1,R)=x=Ll1f(x,R)f(x,x)=x=Ll1f(x,R)x=Ll1(f(x,x1)+[k=0])(p(x):=f(x+1,x))=x=Ll1f(x,R)x=Ll1(p(x1)+[k=0])

L 增大、R 减小、R 增大 同理,不再赘述。

我们可以用桶 O(nm) 预处理出 p(x)。所以剩下的只要拎出 x=Ll1f(x,R) 类的东西求即可,最后再代回来算答案。

接下来就是二次离线部分了。

我们将每一个形如 x=lrf(x,i) 的询问挂在 i 位置上。

然后再用桶扫,同时计算每个位置上的询问。

这部分是 O(nm+nn)nn 是因为二级询问有 O(nn) 个,每个只要 O(1) 回答。

然后就做完了,总时间 O(nm+nn),与开头说的相同。

例题代码:

点击查看代码
//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)

首先先得会树分块。

树分块:P2325 [SCOI2005]王室联邦

为了保证最终莫队复杂度的正确性,我们需要做到:

  • 属于同一块的节点之间的距离不大。

  • 每个块中的节点不能太多也不能太少。

  • 每个节点都要属于一个块。

  • 编号相邻的块之间的距离不能太大。

我们让树分块后依次顺序编号就正好满足了第四个条件了。

分块后的排序方法:若路径 (u,v)u 的时间戳大于 v 那么交换 u,v。然后按照 u 所在块为第一关键字,v 的时间戳为第二关键字排序。

注意这里有一个大坑点:

在指针移动的过程中,我们肯定是让移动前的位置和移动后的位置一起向 lca 靠近。然后利用 vis 标记来
判断这个点是要进入区间还是出区间。

但是这个移动中会出现一个问题,移动时候如果跨过 lca 了会出问题,如下图。

我们按照上面步骤从 (u,v) 移到 (u,v)的时候,两个 lca 都被标记了两次,也就是标记状态没有改变,这是错误的。

所以我们要把 lca 放到最后特判,单独更新。就解决问题了。

形象一点就是:因为若是边权这样处理没有问题,所以我们将树上路径点权移到它连向祖先的边上(这时候要删掉 lca 的点权),成功改为边权,然后正常移动,然后再变回点权(要把 lca 点权加回来)。

P4074 [WC2013] 糖果公园

这甚至是树上带修莫队太毒瘤了

代码被我吃了。由于是缝合题,代码有点长,但是很好理解。

点击查看代码
/*
* 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;}

树上撒点

P3603 雪辉

posted @   ShaoJia  阅读(32)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
点击右上角即可分享
微信分享提示