zoukankan      html  css  js  c++  java
  • Solution: 题解 洛谷 P3380 【模板】二逼平衡树(树套树)的一种二逼写法(带修莫队+权值树状数组)

    二逼平衡树的一种二逼写法

    看到(n,mleqslant 5 imes 10^4)的数据范围,本蒟蒻不禁心生邪念

    大家好,我非常喜欢暴力算法,所以用带修莫队过了这道题

    前置芝士

    1. 带修莫队

    2. 树状数组

    (本蒟蒻的博客还不够完善 sorry,之后会补上这些知识点)

    正题

    假设现在有这样一个神器可以高效满足以下操作:

    1. 加入一个数

    2. 删除一个数

    3. 求一个数的排名

    4. 求某排名的数

    5. 求一个数的前驱

    6. 求一个数的后继

    诶,等等,这不就是普通平衡树吗???

    别急,有更好的方法

    现在假设我们有这个神器数据结构了,接下来就是带修莫队的事情了

    带修莫队比普通莫队多了一个时间维,所以以(b=n^{frac{2}{3}})为长度对所有询问的(l,r)分块,具体来说就是对所有询问按照(lfloor l/b floor,lfloor r/b floor)和时间作三关键字排序

    于是询问就变成了:

    初始化指针(l=1,r=0,t=0),分别表示目前区间左端点,右端点和时间,并且初始化数据结构为空

    设本次询问的指针为(l_q,r_q,t_q)

    于是就要把(l,r,t)移动到(l_q,r_q,t_q),然后从数据结构中直接得到答案

    具体移动方式如下:

    设序列为(a_{1..n})(mt)时间的操作位置为(p_{t}),改为数字(k_{t})

    如果(l>l_q),那么就将(l-1)加入数据结构,(l=l-1)

    如果(l<l_q),那么就将(l)从数据结构中删除,(l=l+1)

    (r)同理

    如果(t<t_q),那么(a_{p_{t+1}})就要改成(k_{t+1}),此时将(a_{p_{t+1}})(k_{t+1})交换,如果(a_{p_{t+1}})(l,r)之间就要修改数据结构,最后(t=t+1)

    如果(t>t_q),那么将(a_{p_{t}})(k_t)换回来,维护数据结构,(t=t-1)

    在这种情况下,算法的时间复杂度已经有了(O(n^frac{5}{3}))的因子

    所以接下来就要看神器的表现了

    解决这类问题,平衡树与线段树都具有(O(log n))的优秀复杂度,但是常数在这里吃不消

    莫队已经是一种离线算法了

    那我们就把离线的方法用到底吧!

    Step 1: 离散化

    将数据从(10^8)范围缩小到(10^5)范围,优化区间处理数据结构的复杂度

    Step 2: 权值树状数组

    线段树的常数还是太大,我们将树状数组扩展为权值树状数组

    首先树状数组的两个基本方法:

    1. ({ m modify})((x,v))表示将序列第(x)位值增加(v)

    2. ({ m query})((x))表示求序列第(x)位前缀和

    在此引入第三个方法({ m kth}(k))表示求最小的(x)使得({ m query}(x)geqslant k),也就是求第(k)

    从定义上看可以二分(x)完成,但是时间复杂度(O(log^2 n))

    在此给出一个(O(log n))的方法:

    设树状数组(t_{1..m}),我们需要进行一些扩展

    定义(rt)为最大的一个(leqslant m)(2)的幂,为树状数组的根节点

    于是({ m lowbit}(rt))是最大的

    把树状数组想象成一棵二叉树,如图(m=10,rt=8),是不是很眼熟

    如果(u)是奇数,那么它是叶子

    定义(u)节点的左儿子(ls)为表示自己左半部分的节点,右儿子(rs)(ls)加上(u)节点长度的的节点

    好混乱,还是上图吧(红线表示左儿子,蓝线表示右儿子)

    给出计算公式(ls=u-{ m lowbit}(u)/2,rs=u+{ m lowbit}(u)/2)

    如果(rs>m),比如(u=8)的情况,就让它不断跳(ls)直到(leqslant m)

    现在开始实现({ m kth}(k))

    初始化当前节点(u=rt),答案(r=m)

    如果(t_ugeqslant k),说明(u)是一个可能答案,于是(r=u,u=ls)继续查找

    如果(t_u<k),说明答案在(u)的右边,于是:

    1. (k=k-t_u),因为(t_{rs})不包含(u)及之前的内容,所以减去

    2. (u=rs),并如果(u>m),重复执行(u=ls)直到(uleqslant m)

    最后,如果(u)是奇数,说明已经到达叶子,返回(r)为答案

    P.s 在代码中为了方便直接设奇数(u)(ls=rs=0)

    于是这样就完成了(O(log n))({ m kth}(k))方法

    只要有了这些,就可以完成全部的查询

    首先为了方便,在数据结构中直接加入一个(2147483647)(-2147483647)

    看一下需要的操作:

    1. 加入一个数(x):直接执行({ m modify}(x,1))

    2. 删除一个数(x):直接执行({ m modify}(x,-1))

    3. 求一个数(x)的排名:因为已经插入(-2147483647),所以({ m query}(x-1))就是答案

    4. 求排名为(k)的数:因为已经插入(-2147483647),所以答案是({ m kth}(k+1))

    5. 求一个数(x)的前驱:一个数的前驱是排名为((x)的排名(-1))的数

    6. 求一个数的后继:一个数的后继是排名为((leqslant x)的数的数量(+1))的数

    (没错,这个权值树状数组可以直接过掉可离线的普通平衡树模板,而且贼快)

    最后是一些卡常技巧:

    1. 打开邪恶的Ofast:满数据开了(10)s,不开(30)s(哇塞)

    2. 事先将所有的(a_i)(k_i)全部变成离散化之后的下标,这样才可以充分利用树状数组的优势:用了(1)s,不用(10)s(好可怕)

    3. 把脸洗干净

    Time complexity: (O(n^frac{5}{3}log n))(真惊险)

    Memory complexity: (O(n))

    细节见代码((3.07)s / (3.19)MB)(虽然时间可能有点长,但是空间绝对碾压树套树)

    P.s 在这里离散化数组为(d),树状数组为(t),因为实在懒得开变量了就直接用(*d)(即(d_0))来储存长度,也就是文中的(m)

    //This program is written by Brian Peng.
    #pragma GCC optimize("Ofast","inline","no-stack-protector")
    #include<bits/stdc++.h>
    using namespace std;
    #define Rd(a) (a=read())
    #define Gc(a) (a=getchar())
    #define Pc(a) putchar(a)
    int read(){
    	int x;char c(getchar());bool k;
    	while(!isdigit(c)&&c^'-')if(Gc(c)==EOF)exit(0);
    	c^'-'?(k=1,x=c&15):k=x=0;
    	while(isdigit(Gc(c)))x=x*10+(c&15);
    	return k?x:-x;
    }
    void wr(int a){
    	if(a<0)Pc('-'),a=-a;
    	if(a<=9)Pc(a|'0');
    	else wr(a/10),Pc((a%10)|'0');
    }
    signed const INF(0x3f3f3f3f),NINF(0xc3c3c3c3);
    long long const LINF(0x3f3f3f3f3f3f3f3fLL),LNINF(0xc3c3c3c3c3c3c3c3LL);
    #define Ps Pc(' ')
    #define Pe Pc('
    ')
    #define Frn0(i,a,b) for(int i(a);i<(b);++i)
    #define Frn1(i,a,b) for(int i(a);i<=(b);++i)
    #define Frn_(i,a,b) for(int i(a);i>=(b);--i)
    #define Mst(a,b) memset(a,b,sizeof(a))
    #define File(a) freopen(a".in","r",stdin),freopen(a".out","w",stdout)
    #define N (50010)
    #define D (100010)
    #define Ls (u&1?0:u-((u&-u)>>1))
    #define Rs (u&1?0:u|((u&-u)>>1))
    int n,m,b,rt,t[D],d[D]{1,-2147483647},a[N],l(1),r,mt,ans[N];
    struct Q{int o,l,r,k,t,i;}q[N];
    struct{int p,k;}o3[N];
    bool cmp(int a,int b,bool c){return a!=b?a<b:c;}
    void mdf(int x,int v){while(x<=*d)t[x]+=v,x+=x&-x;}
    int ps(int x){return lower_bound(d+1,d+*d+1,x)-d;}
    int qry(int x){int r(0);while(x)r+=t[x],x^=x&-x;return r;}
    int kth(int x);
    signed main(){
    	Rd(n),Rd(m),b=pow(n,0.67)+1;
    	Frn1(i,1,n)d[++*d]=Rd(a[i]);
    	Frn1(i,1,m)if(Rd(q[i].o)==3)o3[++mt]={read(),Rd(d[++*d])},--i,--m;
    		else q[i]={q[i].o,read(),read(),read(),mt,i},q[i].o==2?0:(d[++*d]=q[i].k);
    	d[++*d]=2147483647,sort(d+2,d+*d),rt=*d=unique(d+1,d+*d+1)-d-1;
    	Frn1(i,1,n)a[i]=ps(a[i]);
    	Frn1(i,1,mt)o3[i].k=ps(o3[i].k);
    	while(rt^(rt&-rt))rt^=rt&-rt;
    	sort(q+1,q+m+1,[](Q x,Q y){return cmp(x.l/b,y.l/b,cmp(x.r/b,y.r/b,x.t<y.t));});
    	mdf(1,1),mdf(*d,1),mt=0;
    	Frn1(i,1,m){
    		while(r<q[i].r)mdf(a[++r],1);
    		while(l>q[i].l)mdf(a[--l],1);
    		while(r>q[i].r)mdf(a[r--],-1);
    		while(l<q[i].l)mdf(a[l++],-1);
    		while(mt<q[i].t){
    			++mt,swap(a[o3[mt].p],o3[mt].k);
    			if(l<=o3[mt].p&&o3[mt].p<=r)mdf(o3[mt].k,-1),mdf(a[o3[mt].p],1);
    		}
    		while(mt>q[i].t){
    			if(l<=o3[mt].p&&o3[mt].p<=r)mdf(a[o3[mt].p],-1),mdf(o3[mt].k,1);
    			swap(a[o3[mt].p],o3[mt].k),--mt;
    		}
    		switch(q[i].o){
    			case 1:ans[q[i].i]=qry(ps(q[i].k)-1);break;
    			case 2:ans[q[i].i]=d[kth(q[i].k+1)];break;
    			case 4:ans[q[i].i]=d[kth(qry(ps(q[i].k)-1))];break;
    			case 5:ans[q[i].i]=d[kth(qry(ps(q[i].k))+1)];
    		}
    	}
    	Frn1(i,1,m)wr(ans[i]),Pe;
    	exit(0);
    }
    int kth(int k){
    	int u(rt),r(*d);
    	while(u)if(k<=t[u])r=u,u=Ls;
    		else{k-=t[u],u=Rs;while(u>*d)u=Ls;}
    	return r;
    }
    
  • 相关阅读:
    git本地及远程分支回退
    Git怎样撤销一次分支的合并Merge
    git仓库迁移的两种解决方案
    【转】Linux下mysql操作
    Linux下tomcat相关操作
    Linux下top命令详解
    Linux下crontab详解
    Linux下mysql安装
    Linux下RPM包管理
    Linux下用户组、文件权限详解
  • 原文地址:https://www.cnblogs.com/BrianPeng/p/12702028.html
Copyright © 2011-2022 走看看