zkw线段树

\(ZKW\)线段树

今天上午考试题,\(T1\)正解\(O(qlog^2n),\)然后被\(O(qlog^3n)\)的小常数\(zkw\)线段树给踩了...于是乎来补一下这个知识点

形态方面,我们把需要维护的单点信息都存放在同一层,最后一层从左到右依次是\(0\sim n,\)并且是一棵满二叉树

二叉树的父亲表示是\(x>>1,\)和普通线段树没有区别

建立

维护区间和,区间最小值,区间最大值

\(Step_1:\)先确定叶子数目

\(Step_2:\)然后在当前层插入所有的节点

\(Step_3:\)从大到小维护每个节点信息

\(tips:\)为了能够修改,我们把区间最大值和最小值差分(我不是很明白为什么不能直接找到对应的区间然后改完之后上传\(?\))

差分的过程就是,我们父亲的最小值比两个儿子都小,父亲的最大值比两个儿子都大,然后把相同的部分减去即可,然后我们这个区间的最小\(/\)最大值就是一条链的最小\(/\)最大值和

void build()
{
	 while(m<=n) m<<=1;
	 for(int i=1;i<=n;i++)
	 {
	 	 scanf("%d",&Sum[m+i]);
          Mn[m+i]=Mx[m+i]=Sum[m+i];
	 }
	 for(int i=m-1;i>=1;i--)
     {
          Sum[i]=(Sum[i<<1]+Sum[i<<1|1]);
          Mn[i]=min(Mn[i<<1],Mn[i<<1|1]);
          Mx[i]=max(Mx[i<<1],Mx[i<<1|1]);
          Mn[i<<1]-=Mn[i]; Mn[i<<1|1]-=Mn[i];
          Mx[i<<1]-=Mx[i]; Mx[i<<1|1]-=Mx[i];
     }
}

更新

单点修改\(:\)直接从底层往上修改一条链

由于我们维护的是差分,我们同样更新一下差分就好了

void change(int x,int k)//k是增加的值 
{
	 x+=m;
	 Mn[x]+=k; Mx[x]+=k;
	 Sum[1]+=k;
	 for(int A;x>1;x>>=1)
	 {
	 	 Sum[x]+=k;
	 	 A=min(Mn[x],Mn[x^1]);
	 	 Mn[x]-=A,Mn[x^1]-=A,Mn[x>>1]+=A;
	     A=max(Mx[x],Mx[x^1]);
	 	 Mx[x]-=A,Mx[x^1]-=A,Mx[x>>1]+=A;
	 }
}

区间修改:标记永久化即可,如果完全在修改区间内,直接把得到的值修改一下就好了

怎么取寻找区间,假设我们要修改\([l,r],\)\(s=l-1,t=r+1,\)然后在\(s,t\)向上迭代,以\(s\)举例,如果\(s\)节点是父亲节点的左儿子,就让兄弟节点打上标记,当\(s,t\)互为兄弟节点时退出循环

会出现\(t+1=n+1\)的情况,发现没有影响,故不做讨论

我们\(lc,rc\)记录的是目前节点下面有多少覆盖的节点

显然的,我们一直往上迭代,如果满足条件就标记兄弟,那么我们父亲节点的权值会更改

最后我们最上面的部分全部更新一下即可

void change(int s,int t,int k)
{
    int A=0,lc=0,rc=0,len=1; 
    for(s+=m-1,t+=m+1;s^t^1;s>>=1,t>>=1,len<<=1)
	{ 
        if(s&1^1) add[s^1]+=k,lc+=len,Mn[s^1]+=k,Mx[s^1]+=k;
        if(t&1)   add[t^1]+=k,rc+=len,Mn[t^1]+=k,Mx[t^1]+=k;
        Sum[s>>1]+=k*lc,Sum[t>>1]+=k*rc;
        A=min(Mn[s],Mn[s^1]);
        Mn[s]-=A,Mn[s^1]-=A,Mn[s>>1]+=A,
        A=min(Mn[t],Mn[t^1]);
        Mn[t]-=A,Mn[t^1]-=A,Mn[t>>1]+=A;
        A=max(Mx[s],Mx[s^1]);
        Mx[s]-=A,Mx[s^1]-=A,Mx[s>>1]+=A,
        A=max(Mx[t],Mx[t^1]);
        Mx[t]-=A,Mx[t^1]-=A,Mx[t>>1]+=A;
    }
    for(lc+=rc;s>1;s>>=1)
	{
        Sum[s>>1]+=k*lc;
        A=min(Mn[s],Mn[s^1]);
        Mn[s]-=A,Mn[s^1]-=A,Mn[s>>1]+=A,
        A=max(Mx[s],Mx[s^1]);
        Mx[s]-=A,Mx[s^1]-=A,Mx[s>>1]+=A;
    }
}

查询

单点查询\(:\)最大值最小值的话直接差分累和即可,区间和就是单点的值(甚至不需要迭代,直接输出)

区间查询\(:\)我们自下而上查询,和区间修改差别不大,还是如果是完全包含的话,就把区间加上,顺便加上\(add,\)当然如果父亲有标记的话,也要加上下面覆盖的长度,当然区间\(min/max\)就没有必要了,直接把包含位置的\(min/max\)取较小\(/\)大的往上传即可

int query_sum(int s,int t)
{
    int lc=0,rc=0,len=1,ans=0;
    for(s+=m-1,t+=m+1;s^t^1;s>>=1,t>>=1,len<<=1)
	{
        if(s&1^1) ans+=Sum[s^1]+len*add[s^1],lc+=len;
        if(t&1)  ans+=Sum[t^1]+len*add[t^1],rc+=len;
        if(add[s>>1]) ans+=add[s>>1]*lc;
        if(add[t>>1]) ans+=add[t>>1]*rc; 
    }
    for(lc+=rc,s>>=1;s;s>>=1)
    {
    	if(add[s]) ans+=add[s]*lc;
	}
    return ans;
}
int query_min(int s,int t,int L=0,int R=0,int ans=0)
{
    if(s==t) return query_node(s);
    for(s+=m,t+=m;s^t^1;s>>=1,t>>=1)
	{ 
        L+=Mn[s],R+=Mn[t];
        if(s&1^1) L=min(L,Mn[s^1]);
        if(t&1)   R=min(R,Mn[t^1]);
    }
    for(ans=min(L,R),s>>=1;s;s>>=1) ans+=Mn[s];
    return ans;
}
int query_max(int s,int t,int L=0,int R=0,int ans=0){
    if(s==t) return query_node(s);
    for(s+=m,t+=m;s^t^1;s>>=1,t>>=1)
	{
        L+=Mx[s],R+=Mx[t];
        if(s&1^1) L=max(L,Mx[s^1]);
        if(t&1) R=max(R,Mx[t^1]);
    }
    for(ans=max(L,R),s>>=1;s;s>>=1) ans+=Mx[s];
    return ans;
}

查询\(min,max\)\(s,t\)不需要\(-/+1\)

posted @ 2022-05-16 18:32  Authentic_k  阅读(49)  评论(0编辑  收藏  举报