• 带修/在线/树上莫队


    带修莫队

    往常的莫队都是用一个 (l,r) 来标识当前的状态,然后每次将这个 ([l,r]) 的区间不断扩展、缩小
    有了修改,那么可以再增减一维,变成 ([l,r,time])(time) 可以理解为代表了某个时间的数组的状态,比如做一次修改以后,就让时间加一,因为数组的状态改变了
    转移的时候,区间的转移和一般莫队相类似,时间上的转移,就是如果这个时间的修改的点在当前的目标区间(就是当前处理的询问的区间)中,那么就删去数组中的原数,加入修改后的新数,变成新一个状态
    然后数组也要改(因为 (time) 变了,当前的数组肯定对应当前的 (time)),为了下面的操作,新数也要和原数交换(比如这一次是“顺向”的进行这个修改,下一次就应该是时间往后退,倒着进行这个修改)

    排序方式与块大小、复杂度
    类比普通莫队,带修莫队的排序方式应该为以左端点所在的块为第一关键字,右端点所在的块为第二关键字,时间为第三关键字

    关于块大小:这似乎才是莫队难的地方,先设块大小为 (d),则一共 (frac{n}{d}) 块,并假设修改查询操作都和数列长度相同,且每次转移 (O(1)),那么分别分析 (l,r,time) 三个指针,注意下面说的一些情况都是 XX 的块 没变,就是他在块内移动,而不是它本身没有移动

    • (l) 指针,在块内移动时(这种情况就是因为几个询问左端点相同了,那么按照右端点或时间排序造成的),每次 (O(d)),一个 (n) 次询问,那么就是 (O(nd))
      移动到下一个块,每次复杂度 (O(d)),一共 (O(frac{n}{d})) 次,则总复杂度 (O(n))
    • (r) 指针,当 (l,r) 都在同一个块,和之前相同,也是 (n) 次每次 (O(d)),一共 (O(nd))
      (l) 的块没变,(r) 的块改变,移动到下一个块,单次 (O(d)),一共 (O((frac{n}{d})^2)) 次(每次 (l) 的块移动一下,(r) 的块就要回到最左从新移动,所以是块数的平方),总复杂度 (O(frac{n^2}{d}))
      (l,r) 的块都改变,就是上面说的那种 (r) 的块移回最左边重新移动的情况,一共块数次,每次 (O(n)),那么总复杂度 (O(frac{n^2}{d}))
    • (time) 指针,当 (l,r) 的块都不变,(time) 因为在这一段使得 (l,r) 的块不变的询问区间内是排好序的,所以对于这一段询问区间是 (O(n)),那么一共有块数的平方个这样的区间,也就是 (O(frac{n^2}{d^2})),那么总复杂度 (O(frac{n^3}{d^2}))
      (l) 的块不变,(r) 的块改变,以及 (l,r) 的块都改变,这两种情况都是单次 (O(n)),一共 (O((frac{n}{d})^2))(O(frac{n}{d})) 次,复杂度则分别为 (O(frac{n^3}{d^2}))(O(frac{n^2}{d}))

    综合上面的分析,那么整个算法的复杂度,就是 (O(max(nd,frac{n^2}{d},frac{n^3}{d^2}))),由 (dle n) 推出 (frac{n^2}{d}le frac{n^3}{d^2}),则总复杂度化简为 (O(max(nd,frac{n^3}{d^2})),分类讨论一下这两个谁大,就能很简单的得出 (d=n^{frac{2}{3}}) 时最优,是 (O(n^{frac{5}{3}}))

    板子,P1903 [国家集训队]数颜色 / 维护队列:https://www.luogu.com.cn/problem/P1903

    #include<cstdio>
    #include<algorithm>
    #include<iostream>
    #include<cmath>
    #include<map>
    #include<iomanip>
    #include<cstring>
    #define reg register
    #define EN std::puts("")
    #define LL long long
    inline int read(){
    	register int x=0;register int y=1;
    	register char c=std::getchar();
    	while(c<'0'||c>'9'){if(c=='-') y=0;c=std::getchar();}
    	while(c>='0'&&c<='9'){x=x*10+(c^48);c=std::getchar();}
    	return y?x:-x;
    }
    #define N 133340
    struct data{
    	int l,r,id,time;
    }q[N];
    int qtot;
    struct CHANGE{
    	int pos,x;
    }change[N];
    int n,m,B;
    int block[N],a[N];
    int cnt[1000005],ans[N];
    inline int cmp(data a,data b){
    	if(block[a.l]==block[b.l]) return block[a.r]==block[b.r]?a.time<b.time:block[a.r]<block[b.r];
    	else return block[a.l]<block[b.l];
    }
    int num;
    inline void add(int x){if(++cnt[x]==1) num++;}
    inline void del(int x){if(!--cnt[x]) num--;}
    inline void work(int time,int i){
    	if(change[time].pos>=q[i].l&&change[time].pos<=q[i].r){
    		add(change[time].x);del(a[change[time].pos]);
    	}
    	std::swap(change[time].x,a[change[time].pos]);
    	//这一次被改掉的颜色,就是下一次调用这个函数(参数相同)需要修改成的颜色
    }
    int main(){
    	n=read();m=read();B=std::pow(n,2.0/3);
    	for(reg int i=1;i<=n;i++) a[i]=read(),block[i]=block[i-1]+(!((i-1)%B));
    	reg int timenow=1;reg char op;
    	for(reg int i=1;i<=m;i++){
    		op=getchar();
    		while(op!='Q'&&op!='R') op=getchar();
    		if(op=='Q') q[++qtot].l=read(),q[qtot].r=read(),q[qtot].id=qtot,q[qtot].time=timenow;
    		else{
    			change[++timenow].pos=read();change[timenow].x=read();
    		}
    	}
    	std::sort(q+1,q+1+qtot,cmp);
    	reg int l=1,r=0,time=1;
    	for(reg int nexl,nexr,nexT,i=1;i<=qtot;i++){
    		nexl=q[i].l;nexr=q[i].r;nexT=q[i].time;
    		while(r<nexr) add(a[++r]);
    		while(l>nexl) add(a[--l]);
    		while(r>nexr) del(a[r--]);
    		while(l<nexl) del(a[l++]);
    		while(time<nexT) work(++time,i);//先处理完区间,再处理时间
    		while(time>nexT) work(time--,i);
    		ans[q[i].id]=num;
    	}
    	for(reg int i=1;i<=qtot;i++) printf("%d
    ",ans[i]);
    }
    

    树上莫队

    就是每次询问是一个路径上的某某信息
    一般的莫队是按照询问区间的左右端点相关信息来排序,那么树上的莫队,就尝试把树构造成一个序列,把路径变成一个区间
    那么可以用到 dfs 序,不过如果直接把 dfs 序写出来,然后对于一个路径找他们端点之间的区间,发现除了包含路径上的点,还包含了一些子树
    如何消去这些子树产生的影响?一般的 dfs 序都是一个点进入 dfs 栈的时候,就把他写进 dfs 序中,现在,在一个点出栈的时候,把他往 dfs 序里再写一遍

    然后处理这个 dfs 序序列的时候,以往是每往区间加入一个数字,就加上他的贡献,从区间剔除一个数字,就减他的贡献。而现在,同等的对待往区间加入或从区间删除,如果上一次是加贡献,这次就减贡献,反之亦然(具体实现用一个 vis 记录,每次异或)
    为什么要这样?如果第二次遇见这个数,遇见的是序列上同一个数(dfs 序序列上每个数都有两个),那么这次减贡献是显然的,如果不是同一个,说明这次遇见的这个数,是标志着这个数出 dfs 栈,那么应该减去贡献来避免刚才说的那些子树对路径上答案的影响
    这样,对于那些被包含进去的本不应该存在的子树,消除了他们的贡献

    其他的和序列上的莫队一样了

    树上带修莫队

    就是把在特殊处理的 dfs 序上,按照上面的方式跑莫队改成了跑带修莫队呗
    例题:P4074 [WC2013]糖果公园
    https://www.luogu.com.cn/problem/P4074
    https://darkbzoj.tk/problem/3052

    这题如何加上、减去一个数的贡献都是很显然的,就几乎是个树上带修莫队的板子了

    #include<cstdio>
    #include<algorithm>
    #include<iostream>
    #include<cmath>
    #include<map>
    #include<iomanip>
    #include<cstring>
    #define reg register
    #define EN std::puts("")
    #define LL long long
    inline int read(){
    	register int x=0;register int y=1;
    	register char c=std::getchar();
    	while(c<'0'||c>'9'){if(c=='-') y=0;c=std::getchar();}
    	while(c>='0'&&c<='9'){x=x*10+(c^48);c=std::getchar();}
    	return y?x:-x;
    }
    #define N 200005
    #define M 200005
    struct graph{
    	int fir[N],nex[M],to[M],tot;
    	inline void add(int u,int v){
    		to[++tot]=v;
    		nex[tot]=fir[u];fir[u]=tot;
    	}
    }G;
    int n,m,B;
    struct data{
    	int l,r,time,id;
    }q[N];
    int qtot;
    struct Change{
    	int pos,val;
    }change[N];
    int id[N],dfnin[N],dfnout[N],dfscnt;
    int deep[N],fa[20][N];
    int block[N],col[N],W[N],val[N],vis[N],num[N];
    long long Ans[N];
    void dfs(int u){
    	id[++dfscnt]=u;dfnin[u]=dfscnt;
    	deep[u]=deep[fa[0][u]]+1;
    	for(reg int i=G.fir[u],v;i;i=G.nex[i]){
    		v=G.to[i];
    		if(v==fa[0][u]) continue;
    		fa[0][v]=u;dfs(v);
    	}
    	id[++dfscnt]=u;dfnout[u]=dfscnt;
    }
    inline int lca(reg int u,reg int v){
    	if(deep[u]<deep[v]) u^=v,v^=u,u^=v;
    	for(reg int i=18;~i;i--)if(deep[fa[i][u]]>=deep[v]) u=fa[i][u];
    	if(u==v) return u;
    	for(reg int i=18;~i;i--)if(fa[i][u]^fa[i][v]) u=fa[i][u],v=fa[i][v];
    	return fa[0][u];
    }
    inline int cmp(data a,data b){
    	if(block[a.l]==block[b.l]) return block[a.r]==block[b.r]?a.time<b.time:block[a.r]<block[b.r];
    	return block[a.l]<block[b.l];
    }
    inline void init_q(int Q){
    	int timenow=1;
    	for(reg int i=1,x,y,op;i<=Q;i++){
    		op=read();x=read();y=read();
    		if(op){
    			if(dfnin[x]>dfnin[y]) x^=y,y^=x,x^=y;
    			q[++qtot]=(data){lca(x,y)==x?dfnin[x]:dfnout[x],dfnin[y],timenow,qtot};
    		}
    		else{
    			change[++timenow]=(Change){x,y};
    		}
    	}
    	std::sort(q+1,q+1+qtot,cmp);
    }
    LL ans;
    inline void add(int x){
    	if(vis[x]) ans-=(long long)val[col[x]]*W[num[col[x]]--];
    	else ans+=(long long)val[col[x]]*W[++num[col[x]]];
    	vis[x]^=1;
    }
    inline void chtime(Change &x){//传引用!!!
    	int lastcol=col[x.pos];
    	if(vis[x.pos]){//计算过这个点,需要先把他的贡献减去,修改完再加回来
    		add(x.pos);
    		col[x.pos]=x.val;
    		add(x.pos);
    	}
    	else col[x.pos]=x.val;
    	x.val=lastcol;
    }
    int main(){
    	n=read();m=read();int Q=read();
    	B=std::pow(2*n,2.0/3);
    	for(reg int i=1;i<=2*n;i++) block[i]=block[i-1]+(!((i-1)%B));
    	for(reg int i=1;i<=m;i++) val[i]=read();
    	for(reg int i=1;i<=n;i++) W[i]=read();
    	for(reg int i=1,u,v;i<n;i++){
    		u=read();v=read();
    		G.add(u,v);G.add(v,u);
    	}
    	for(reg int i=1;i<=n;i++) col[i]=read();
    	dfs(1);
    	for(reg int i=1;i<=18;i++)
    		for(reg int j=1;j<=n;j++) fa[i][j]=fa[i-1][fa[i-1][j]];
    	init_q(Q);
    	reg int l=1,r=0,time=1;
    	for(reg int i=1,nexl,nexr,nexT;i<=qtot;i++){
    //			printf("now : %d %d %d
    ",l,r,time);
    		nexl=q[i].l;nexr=q[i].r;nexT=q[i].time;
    		while(r<nexr) add(id[++r]);
    		while(l>nexl) add(id[--l]);
    		while(r>nexr) add(id[r--]);
    		while(l<nexl) add(id[l++]);
    		while(time<nexT) chtime(change[++time]);
    		while(time>nexT) chtime(change[time--]);
    		int Lca=lca(id[l],id[r]);
    		if((Lca^id[l])&&(Lca^id[r])){//lca 需要另外计算
    			add(Lca);
    			Ans[q[i].id]=ans;
    			add(Lca);
    		}
    		else Ans[q[i].id]=ans;
    //			printf("id : %d  ans : %lld
    ",q[i].id,ans);
    	}
    	for(reg int i=1;i<=qtot;i++) printf("%lld
    ",Ans[i]);
    	return 0;
    }
    

    在线莫队

    在线莫队的操作是从这里看的,orz 诗乃:https://www.luogu.com.cn/blog/asadashino/moqueue

    一般的莫队是把询问排序的,打乱无法保证在原顺序中,回答下一个询问前一定知道上一个询问的答案,所以强制在线的情况下想排序询问肯定不可能,但是莫队那种转移区间的方式还是可以用的
    方法就是选取一些“特征点”,然后预处理任意两个“特征点”之间的答案和转移这些区间要用到的信息,称之为特征区间,设最小特征区间的长度为 (d),那么预处理的复杂度就是 (O(ncdot frac{n}{d})=O(frac{n^2}{d}))(就是固定住一个点,另一个点不断向外扩展到下一个特征点,然后扩展到最右就移动固定的点,固定的点移动 (frac{n}{d}) 次),然后询问就通过移动区间到任意一个特征区间,单次复杂度 (O(d)),显然 (d=sqrt n) 时最优,那么总复杂度依然 (O(nsqrt n))
    所以其实还是比较简单的

    但是之前说过要维护特征区间的转移信息,肯定不能每个区间都单独维护,如果这个信息满足可减性,那么直接用前缀和的思想减一下就行,否则可能得用可持久化等东西?反正我不会就是了

    在线带修莫队

    去看诗乃的博客吧,看了看好复杂的样子

  • 相关阅读:
    孙剑云访谈【转载】
    继承几近失传的经典吟诵-余觉中
    俞净意公遇灶神记
    吟诵,不为吟诵
    .NET中使用Redis
    redis密码设置、访问权限控制等安全设置
    Mock框架
    日记 2016年8月9日(周二)
    Notepad++前端开发常用插件介绍
    [Android Tips] 8. Install apk on multiple connected devices
  • 原文地址:https://www.cnblogs.com/suxxsfe/p/13538313.html
Copyright © 2020-2023  润新知