lct201714
2024-10-09 19:23:18
若存在序列上的一个区间
显然
对于
不妨设块长为
移动还可能跨越块,所以还要加上
总复杂度即为
由不等式分析 在
struct Query{
int id,l,r;
bool operator < (const Query &tmp) const { return l/B==tmp.l/B?r<tmp.r:l/B<tmp.l/B;}
}q[N];
void add(int pos,int &Ans) {/*新增节点信息,记录Ans*/}
void del(int pos,int &Ans) {/*删除节点信息,记录Ans*/}
void solve(){
int l=1,r=0,Ans=0; // 初始为空
sort(q+1,q+m+1);
For(i,1,m){
const Query &que=q[i];
// 优先进行区间的扩张,防止区间为空产生不必要错误
while(l>que.l) add(--l,Ans);
while(r<que.r) add(++r,Ans);
// -------------------------
while(l<que.l) del(l++,Ans);
while(r>que.r) del(r--,Ans);
ans[que.id]=Ans;
}
}
int main(){
read(n); read(m);
For(i,1,n) read(n);
B=n/sqrt(m); if(!B) B=sqrt(n); // 防止 n<sqrt(m) 导致块长为 0
For(i,1,m) q[i].id=i,read(q[i].l),read(q[i].r);
solve();
For(i,1,m) Print(ans[i]),putchar('\n');
return 0;
}
排序时将偶数块的询问右端点从小到大排序,奇数块的询问右端点从小到大排序,以减少右端点的移动范围。
// 奇偶化排序 优化
inline bool operator < (const Query &tmp) const {
if(l/B!=tmp.l/B) return l<tmp.l;
if((l/B)&1) return r<tmp.r;
return r>tmp.r;
}
将块长设置为某个常数,可能对于代码运行速度会有一定的提升。
普通的莫队是不支持修改操作的。
但我们可以强行为莫队添加一维时间轴,对于每个修改状态下的答案都进行计算。
区间由
区间移动演变为六个行为:
当块长取
认为
时间复杂度可以达到
int n,m,B;
struct Query{
int id,l,r,t;
bool operator < (const Query &tmp) const {
if(l/B!=tmp.l/B) return l<tmp.l;
if(r/B!=tmp.r/B) return r<tmp.r;
return t<tmp.t;
}
}q[N];
struct Change{int p,c;}c[N];
void add(int pos){/*插入信息*/}
void del(int pos){/*删除信息*/}
void solve(){
int l=1,r=0,tim=0;
sort(q+1,q+kp+1);
For(i,1,kp){
const Query& que=q[i];
while(l>que.l) add(--l);
while(r<que.r) add(++r);
while(l<que.l) del(l++);
while(r>que.r) del(r--);
while(tim<que.t){
tim++;
if(c[tim].p>=l&&c[tim].p<=r){
// del(), add();
}
}
while(tim>que.t){
if(c[tim].p>=l&&c[tim].p<=r){
// del(),add();
}
tim--;
}
ans[que.id]=Ans;
}
}
int main(){
read(n),read(m);
For(i,1,n) read(a[i]);
For(i,1,m){
int op,x,y; read(op),read(x),read(y);
if(op==1) c[++tot]=(Change){x,y};
else q[++kp]=(Query){kp,x,y,tot};
}
B=cbrt(1.0*n*max(1,tot))+1; // == pow(1.0*n*tot,1.0/3);
solve();
For(i,1,kp) Print(ans[i]),putchar('\n');
return 0;
}
普通的莫队只能针对于序列问题进行求解。
处理树上问题时,有一种显然的想法是将树转录为一段序列。
如何将树转录为序列,是将算法进行扩展的瓶颈所在。
用平时使用的
这时,欧拉序列的功能就体现出来了。
树上每个节点第一次遍历和回溯时,都将其加入序列,以此方式产生的序列即为欧拉序列。
以此树为例:
欧拉序列为:
这时我们可以发现,对于树上路径问题,
第一次经过节点时将它计入答案,第二次经过则删除它,中间的节点不会对询问产生实际影响,
这样做同时保证了答案向周围区间扩展是成立的。
故 基于欧拉序列,树上莫队是完全可以实现的。
询问离线,维护树的欧拉序列,维护莫队操作即可。
特别的,在树上
不妨设
1. 若
2. 若
处理询问时:
第一次经过节点则计入新的信息,第二次经过则将信息删除即可。
此过程可以用
int n,m,B,f[N],cnt;
struct Edge{int v,nxt;}e[N<<1];
void insert(int u,int v){e[++cnt]=(Edge){v,f[u]}; f[u]=cnt;}
struct Query{
int id,l,r;
int p; // 存储lca
bool operator < (const Query &tmp) const { return l/B==tmp.l/B?r<tmp.r:l/B<tmp.l/B;}
}q[N];
int dep[N],fa[N][18],top,seq[N],first[N],last[N];
void dfs(int u){
seq[++top]=u; first[u]=top;
dep[u]=dep[fa[u][0]]+1;
for(int i=1;i<=15;i++) fa[u][i]=fa[fa[u][i-1]][i-1];
for(int i=f[u];i;i=e[i].nxt){
int v=e[i].v;
if(v!=fa[u][0]){
fa[v][0]=u;
dfs(v);
}
}
seq[++top]=u; last[u]=top;
}
int lca(int u,int v){
if(dep[u]<dep[v]) swap(u,v);
for(int i=15;i>=0;i--) if(dep[fa[u][i]]>=dep[v]) u=fa[u][i];
if(u==v) return u;
for(int i=15;i>=0;i--){
if(fa[u][i]!=fa[v][i]){
u=fa[u][i];
v=fa[v][i];
}
}
return fa[u][0];
}
void rev(int pos,int &Ans){
vis[pos]^=1;
if(!vis[pos]){/*删除信息*/}
else{/*插入信息*/ }
}
void solve(){
int l=1,r=0,Ans=0;
sort(q+1,q+m+1);
For(i,1,m){
const Query &que=q[i];
while(l>que.l) rev(seq[--l],Ans);
while(r<que.r) rev(seq[++r],Ans);
while(l<que.l) rev(seq[l++],Ans);
while(r>que.r) rev(seq[r--],Ans);
if(que.p) rev(que.p,Ans);
ans[que.id]=Ans;
if(que.p) rev(que.p,Ans);
}
}
int main(){
read(n); read(m);
For(i,1,n) read(a[i]);
For(i,1,n-1){
int u,v; read(u),read(v);
insert(u,v),insert(v,u);
}
dfs(1); B=sqrt(top);
For(i,1,m){
int u,v; read(u),read(v);
if(first[u]>first[v]) swap(u,v);
int p=lca(u,v);
if(u==p) q[i]=(Query){i,first[u],first[v],0};
else q[i]=(Query){i,last[u],first[v],p};
}
solve();
For(i,1,m) cout<<ans[i],putchar('\n');
return 0;
}
在树上莫队化树上问题为序列问题的思想基础下,将莫队以带修莫队的方式进行维护。
块长 :
复杂度
上文是将树上问题转化为序列问题,下文将补充直接在树上操作的树上莫队。
(但 真·树上莫队 的泛用性似乎并不多,故不重点介绍。)
排序方式:
基于树分块思想:
令
容易想到:对于根,可能会剩下一些点,于是将这些点分在最后一个块里。
操作方法:
用栈维护当前节点作为父节点访问它的子节点,
当从栈顶到父节点的距离大于希望块的大小时,
弹出这部分元素分为一块,最后剩余的一块单独作为一块。
最后的排序方法:
若第一维时间戳大于第二维,交换它们,按第一维所属块为第一关键字,第二维时间戳为第二关键字排序。
普通莫队在处理某些问题时,如区间最大值等。
会发现莫队的插入操作很容易实现,但删除操作难以快速进行。
这时,我们可以放弃删除操作,只进行插入操作,恢复过程交给回滚来进行。
同理,增加操作难以进行时,仍然可以采取此类思想处理问题。
以只进行增加的回滚莫队为例。
排序后,按顺序处理询问:
枚举每一个对于询问分出的块,将莫队区间左右端点分别设为块的左端点加
如果询问的左右端点所属的块相同,那么直接扫描区间回答询问;
如果询问的左右端点所属的块不同:
如果询问的右端点大于莫队区间的右端点,那么不断扩展右端点直至莫队区间的右端点等于询问的右端点;
不断扩展莫队区间的左端点直至莫队区间的左端点等于询问的左端点;
回答询问;
撤销莫队区间左端点的改动,使莫队区间的左端点回滚到 B 的右端点加 1。
假设回滚莫队的分块大小是
对于左、右端点在同一个块内的询问,可以在
对于其他询问,考虑左端点在相同块内的询问,它们的右端点单调递增,移动右端点的时间复杂度是
而左端点单次询问的移动不超过
struct Query{
int id,l,r;
bool operator < (const Query &tmp) const {return P[l]==P[tmp.l]?r<tmp.r:P[l]<P[tmp.l];}
}q[N];
int Tot,L[N],R[N];
void build(){
B=n/sqrt(m); if(B==0) B=sqrt(n); // 防止 n<sqrt(m) 导致 B=0
Tot=n/B; // 预处理块的左右端点
For(i,1,Tot) L[i]=(i-1)*B+1, R[i]=i*B;
if(R[Tot]<n) Tot++, L[Tot]=R[Tot-1]+1, R[Tot]=n;
For(i,1,Tot) For(j,L[i],R[i]) P[j]=i;
}
void solve(){
sort(q+1,q+m+1);
for(int i=1,p=1;p<=Tot;p++){ // 枚举块
// 清空标记 执行新的块内的操作
int l=R[p]+1,r=R[p],Ans=0;
for(;P[q[i].l]==p&&i<=m;i++){
const Query &que=q[i];
if(P[que.l]==P[que.r]){
// 在块内的区间暴力进行操作
continue ;
}
while(r<que.r){
++r;add_R(); // 扩展右端点
}
int l_=l; long long tmp=Ans;
while(l>que.l){
--l;add_L(); // 扩展左端点
}
ans[que.id]=Ans;
while(l<l_){ // 以下回滚
Delete_sth(); // 还原状态 如将数组恢复原状
l++;
}
Ans=tmp;
}
}
}
由于回滚莫队本身访问顺序的不同,
故在实际运用中一般不与奇偶排序优化一同使用。
暂无
(蒟蒻作者还没学会)
一些简单的莫队问题可以在完成区间移动后直接
但像以下问题,如:
在
求在区间
此类问题难以以
void init(){ // 值域分块
B=sqrt(n); Tot=n/B;
For(i,1,Tot) L[i]=(i-1)*B+1, R[i]=i*B;
if(R[Tot]<n) Tot++, L[Tot]=R[Tot-1]+1, R[Tot]=n;
For(i,1,Tot) For(j,L[i],R[i]) P[j]=i;
}
int cnt[N],bl[N],blo[N];
PII ask(int l,int r){
int res=0,kp=0;
if(P[l]==P[r]){
For(i,l,r){
if(cnt[i]) res++;
kp+=cnt[i];
}
}
else{
For(i,l,R[P[l]]){
if(cnt[i]) res++;
kp+=cnt[i];
}
For(i,L[P[r]],r){
if(cnt[i]) res++;
kp+=cnt[i];
}
For(i,P[l]+1,P[r]-1){
res+=bl[i];
kp+=blo[i];
}
}
return make_pair(kp,res);
}
void solve(){
int l=1,r=0;
sort(q+1,q+m+1);
For(i,1,m){
const Query &que=q[i];
while(l>que.l) add(s[--l]);
while(r<que.r) add(s[++r]);
while(l<que.l) del(s[l++]);
while(r>que.r) del(s[r--]);
ans[que.id]=ask(que.a,que.b);
}
}