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\)